Libraries
library(tidyverse)
library(imputeTS)
library(patchwork)
library(seastests)
library(trend)
library(forecast)
Dataset
link <- "https://gist.githubusercontent.com/novrisuhermi/cc55e030a8d73a6b286976bd8d48dec2/raw/e610da2a0043e260d78692e2f82e1c44ea936ee4/ElectricProduction.csv"
df <- read.csv(link)
df$DATE <- as.Date(df$DATE, format = "%m/%d/%Y")
p <- ggplot(df, aes(x = DATE, y = IPG2211A2N)) +
geom_line(colour = "steelblue4", linewidth = 0.5) +
scale_x_date(
breaks = seq(as.Date("1985-01-01"), as.Date("2015-01-01"), by = "5 years"),
date_labels = "%Y"
) +
labs(
title = "Monthly Electric Production",
subtitle = "January 1985 - January 2018",
x = "Year",
y = "Production index"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
p

Seasonality and Trend
y <- ts(df$IPG2211A2N, start = c(1985, 1), frequency = 12)
Seasonal Plot
p_season <- ggseasonplot(y, continuous = TRUE) +
labs(title = "Seasonal Plot", x = "Month", y = "Production index", colour = "Year") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
p_subseries <- ggsubseriesplot(y) +
labs(title = "Seasonal Subseries Plot", subtitle = "Blue lines = monthly means",
x = "Month", y = "Production index") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
p_season | p_subseries

Seasonality Tests
# H0: no seasonality. By default the tests use the first-differenced series,
# which removes the trend.
kw(y) # Kruskal-Wallis test
Test used: Kruskall Wallis
Test statistic: 345.35
P-value: 0
fried(y) # Friedman test
Test used: Friedman rank
Test statistic: 323.19
P-value: 0
qs(y) # QS test (autocorrelation at seasonal lags)
Test used: QS
Test statistic: 610.31
P-value: 0
isSeasonal(y, test = "combined") # Webel-Ollech combined test: TRUE = seasonal
[1] TRUE
Trend Tests
# H0: no monotonic trend
smk.test(y) # Seasonal Mann-Kendall (accounts for seasonality)
Seasonal Mann-Kendall trend test (Hirsch-Slack test)
data: y
z = 24.471, p-value < 2.2e-16
alternative hypothesis: true S is not equal to 0
sample estimates:
S varS
5493 50369
mk.test(y) # Ordinary Mann-Kendall, for comparison
Mann-Kendall trend test
data: y
z = 19.125, n = 397, p-value < 2.2e-16
alternative hypothesis: true S is not equal to 0
sample estimates:
S varS tau
5.052400e+04 6.978466e+06 6.427499e-01
# Seasonal Sen's slope = change per year; needs complete years, so Jan 2018 is dropped
sea.sens.slope(window(y, end = c(2017, 12)))
[1] 1.392249
Simulating Missing Values
inject_gaps <- function(x, prop = 0.15, gap_len = 1:40, gap_prob = 1 / gap_len,
cover = list(single = 1, short = 2:5, long = 6:12),
min_sep = 1, buffer = 1) {
n <- length(x)
m <- round(prop * n)
# 1. Gap lengths: one per class in `cover`, the rest random, summing to m
L <- vapply(cover, function(r) r[sample.int(length(r), 1)], numeric(1))
if (sum(L) > m) stop("prop is too small for the gap classes in `cover`.")
L <- c(L, gap_len[sample.int(length(gap_len), m, replace = TRUE, prob = gap_prob)])
k <- which(cumsum(L) >= m)[1]
L <- L[seq_len(k)]
L[k] <- m - sum(L[-k])
L <- unname(L[sample.int(k)])
# 2. Observed points that are free to be placed anywhere
free <- n - m - 2 * buffer - (k - 1) * min_sep
if (free < 0) stop("Gaps do not fit: reduce prop, min_sep or buffer.")
# 3. Spread the free points randomly over the k + 1 spaces (stars and bars)
bars <- sort(sample.int(free + k, k))
extra <- diff(c(0, bars, free + k + 1)) - 1
# 4. Gap start positions, then set the gaps to NA
space <- c(buffer, rep(min_sep, k - 1)) + extra[seq_len(k)]
start <- cumsum(space + c(0, L[-k])) + 1
x[sequence(L, from = start)] <- NA
x
}
set.seed(2026)
df_missing <- df %>%
mutate(IPG2211A2N = inject_gaps(IPG2211A2N, prop = 0.20))
df_missing
# Left: series with missing regions (x = date)
p_series <- ggplot_na_distribution(df_missing$IPG2211A2N,
x_axis_labels = df_missing$DATE,
xlab = "Year",
ylab = "Production index") +
scale_x_date(
breaks = seq(as.Date("1985-01-01"), as.Date("2015-01-01"), by = "5 years"),
date_labels = "%Y"
)
# Right: share of NA per year (x = observation index, relabelled as years)
p_interval <- suppressMessages(
ggplot_na_distribution2(df_missing$IPG2211A2N, interval_size = 12, xlab = "Year") +
scale_x_continuous(breaks = seq(0, 360, by = 60), # index 0, 60, ... = Jan 1985, Jan 1990, ...
labels = seq(1985, 2015, by = 5),
expand = c(0, 0))
)
p_series | p_interval

Imputation
y_na <- ts(df_missing$IPG2211A2N, start = c(1985, 1), frequency = 12)
quiet_structts <- function(expr) {
withCallingHandlers(expr, warning = function(w) {
if (grepl("possible convergence problem", conditionMessage(w))) invokeRestart("muffleWarning")
})
}
set.seed(2026) # na_random() and the "random" algorithms draw random values
imputations <- list(
# Mean-based
mean = na_mean(y_na, option = "mean"),
median = na_mean(y_na, option = "median"),
mode = na_mean(y_na, option = "mode"),
harmonic = na_mean(y_na, option = "harmonic"),
geometric = na_mean(y_na, option = "geometric"),
# Random values between the observed minimum and maximum
random = na_random(y_na),
# Last observation carried forward / next observation carried backward
locf = na_locf(y_na, option = "locf"),
nocb = na_locf(y_na, option = "nocb"),
# Interpolation
linear = na_interpolation(y_na, option = "linear"),
spline = na_interpolation(y_na, option = "spline"),
stine = na_interpolation(y_na, option = "stine"),
# Weighted moving average (k = 4 observations on each side)
ma_simple = na_ma(y_na, k = 4, weighting = "simple"),
ma_linear = na_ma(y_na, k = 4, weighting = "linear"),
ma_exponential = na_ma(y_na, k = 4, weighting = "exponential"),
# Kalman smoothing
kalman_structts = quiet_structts(na_kalman(y_na, model = "StructTS")),
kalman_arima = na_kalman(y_na, model = "auto.arima"),
# Seasonal decomposition: impute the deseasonalised series, then add the season back
seadec_interpolation = na_seadec(y_na, algorithm = "interpolation"),
seadec_locf = na_seadec(y_na, algorithm = "locf"),
seadec_mean = na_seadec(y_na, algorithm = "mean"),
seadec_random = na_seadec(y_na, algorithm = "random"),
seadec_kalman = quiet_structts(na_seadec(y_na, algorithm = "kalman")),
seadec_ma = na_seadec(y_na, algorithm = "ma"),
# Seasonal split: impute each calendar month's subseries separately
seasplit_interpolation = na_seasplit(y_na, algorithm = "interpolation"),
seasplit_locf = na_seasplit(y_na, algorithm = "locf"),
seasplit_mean = na_seasplit(y_na, algorithm = "mean"),
seasplit_random = na_seasplit(y_na, algorithm = "random"),
seasplit_kalman = quiet_structts(na_seasplit(y_na, algorithm = "kalman")),
seasplit_ma = na_seasplit(y_na, algorithm = "ma")
)
df_imputed <- df_missing %>%
select(DATE, observed = IPG2211A2N) %>%
mutate(actual = df$IPG2211A2N, .after = DATE) %>%
bind_cols(as_tibble(map(imputations, as.numeric)))
df_imputed
Evaluation
RMSE and MAPE
Accuracy is computed only on the m
imputed (originally missing) points \mathcal{M}:
\text{RMSE} = \sqrt{\frac{1}{m}\sum_{t \in \mathcal{M}} (\hat{y}_t -
y_t)^2}, \qquad
\text{MAPE} = \frac{100}{m}\sum_{t \in \mathcal{M}} \left\lvert
\frac{\hat{y}_t - y_t}{y_t} \right\rvert
method_groups <- list(
"Mean-based" = c("mean", "median", "mode", "harmonic", "geometric"),
"Random" = "random",
"LOCF / NOCB" = c("locf", "nocb"),
"Interpolation" = c("linear", "spline", "stine"),
"Moving Average" = c("ma_simple", "ma_linear", "ma_exponential"),
"Kalman Smoothing" = c("kalman_structts", "kalman_arima"),
"Seasonal Decomposition" = paste0("seadec_", c("interpolation", "locf", "mean", "random", "kalman", "ma")),
"Seasonal Split" = paste0("seasplit_", c("interpolation", "locf", "mean", "random", "kalman", "ma"))
)
# RMSE and MAPE on the imputed points only
evaluation <- df_imputed %>%
filter(is.na(observed)) %>%
pivot_longer(-c(DATE, actual, observed), names_to = "method", values_to = "imputed") %>%
group_by(method) %>%
summarise(RMSE = sqrt(mean((imputed - actual)^2)),
MAPE = 100 * mean(abs(imputed - actual) / actual)) %>%
left_join(enframe(method_groups, name = "group", value = "method") %>% unnest(method),
by = "method") %>%
relocate(group) %>%
arrange(RMSE)
# Display: ranked table, rounded, all 28 rows on one page (chunk option rows.print)
evaluation %>%
mutate(Rank = row_number(), across(c(RMSE, MAPE), ~ round(.x, 3))) %>%
select(Rank, Family = group, Method = method, RMSE, `MAPE (%)` = MAPE)
Imputed Time Series Visualization
plot_imputations <- function(group, ncol = 2) {
methods <- method_groups[[group]]
labels <- evaluation %>%
mutate(label = sprintf("%s (RMSE %.2f, MAPE %.2f%%)", method, RMSE, MAPE))
df_imputed %>%
select(DATE, actual, observed, all_of(methods)) %>%
pivot_longer(all_of(methods), names_to = "method", values_to = "imputed") %>%
mutate(method = factor(method, levels = methods,
labels = labels$label[match(methods, labels$method)]),
imputed = if_else(is.na(observed), imputed, NA_real_)) %>%
ggplot(aes(x = DATE)) +
geom_line(aes(y = actual), colour = "grey70", linewidth = 0.3) +
geom_line(aes(y = observed), colour = "steelblue4", linewidth = 0.4, na.rm = TRUE) +
geom_point(aes(y = imputed), colour = "firebrick", size = 0.8, na.rm = TRUE) +
facet_wrap(~ method, ncol = ncol) +
scale_x_date(
breaks = seq(as.Date("1985-01-01"), as.Date("2015-01-01"), by = "5 years"),
date_labels = "%Y"
) +
labs(title = group,
subtitle = "Grey = actual (hidden) values, blue = observed, red = imputed",
x = "Year", y = "Production index") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold"))
}
Mean-based
plot_imputations("Mean-based")

Random
plot_imputations("Random", ncol = 1)

LOCF / NOCB
plot_imputations("LOCF / NOCB")

Interpolation
plot_imputations("Interpolation")

Moving Average
plot_imputations("Moving Average")

Kalman Smoothing
plot_imputations("Kalman Smoothing")

Seasonal Decomposition
plot_imputations("Seasonal Decomposition")

Seasonal Split
plot_imputations("Seasonal Split")

LS0tCnRpdGxlOiAiVGltZSBTZXJpZXMgSW1wdXRhdGlvbiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQogICAgbWF0aF9tZXRob2Q6IGthdGV4Ci0tLQoKIyMgTGlicmFyaWVzCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShpbXB1dGVUUykKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoc2Vhc3Rlc3RzKQpsaWJyYXJ5KHRyZW5kKQpsaWJyYXJ5KGZvcmVjYXN0KQpgYGAKCiMjIERhdGFzZXQKCmBgYHtyfQpsaW5rIDwtICJodHRwczovL2dpc3QuZ2l0aHVidXNlcmNvbnRlbnQuY29tL25vdnJpc3VoZXJtaS9jYzU1ZTAzMGE4ZDczYTZiMjg2OTc2YmQ4ZDQ4ZGVjMi9yYXcvZTYxMGRhMmEwMDQzZTI2MGQ3ODY5MmUyZjgyZTFjNDRlYTkzNmVlNC9FbGVjdHJpY1Byb2R1Y3Rpb24uY3N2IgpkZiA8LSByZWFkLmNzdihsaW5rKQpkZiREQVRFIDwtIGFzLkRhdGUoZGYkREFURSwgZm9ybWF0ID0gIiVtLyVkLyVZIikKYGBgCgpgYGB7cn0KcCA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gREFURSwgeSA9IElQRzIyMTFBMk4pKSArCiAgZ2VvbV9saW5lKGNvbG91ciA9ICJzdGVlbGJsdWU0IiwgbGluZXdpZHRoID0gMC41KSArCiAgc2NhbGVfeF9kYXRlKAogICAgYnJlYWtzID0gc2VxKGFzLkRhdGUoIjE5ODUtMDEtMDEiKSwgYXMuRGF0ZSgiMjAxNS0wMS0wMSIpLCBieSA9ICI1IHllYXJzIiksCiAgICBkYXRlX2xhYmVscyA9ICIlWSIKICApICsKICBsYWJzKAogICAgdGl0bGUgICAgPSAiTW9udGhseSBFbGVjdHJpYyBQcm9kdWN0aW9uIiwKICAgIHN1YnRpdGxlID0gIkphbnVhcnkgMTk4NSAtIEphbnVhcnkgMjAxOCIsCiAgICB4ICAgICAgICA9ICJZZWFyIiwKICAgIHkgICAgICAgID0gIlByb2R1Y3Rpb24gaW5kZXgiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKwogIHRoZW1lKAogICAgcGxvdC50aXRsZSAgICAgICA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkKICApCgpwCmBgYAoKCgojIyBTZWFzb25hbGl0eSBhbmQgVHJlbmQKCmBgYHtyfQp5IDwtIHRzKGRmJElQRzIyMTFBMk4sIHN0YXJ0ID0gYygxOTg1LCAxKSwgZnJlcXVlbmN5ID0gMTIpCmBgYAoKIyMjIFNlYXNvbmFsIFBsb3QKCmBgYHtyIGZpZy5oZWlnaHQ9NC41LCBmaWcud2lkdGg9MTIsIHdhcm5pbmc9RkFMU0V9CnBfc2Vhc29uIDwtIGdnc2Vhc29ucGxvdCh5LCBjb250aW51b3VzID0gVFJVRSkgKwogIGxhYnModGl0bGUgPSAiU2Vhc29uYWwgUGxvdCIsIHggPSAiTW9udGgiLCB5ID0gIlByb2R1Y3Rpb24gaW5kZXgiLCBjb2xvdXIgPSAiWWVhciIpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSkKCnBfc3Vic2VyaWVzIDwtIGdnc3Vic2VyaWVzcGxvdCh5KSArCiAgbGFicyh0aXRsZSA9ICJTZWFzb25hbCBTdWJzZXJpZXMgUGxvdCIsIHN1YnRpdGxlID0gIkJsdWUgbGluZXMgPSBtb250aGx5IG1lYW5zIiwKICAgICAgIHggPSAiTW9udGgiLCB5ID0gIlByb2R1Y3Rpb24gaW5kZXgiKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpCgpwX3NlYXNvbiB8IHBfc3Vic2VyaWVzCmBgYAoKIyMjIFNlYXNvbmFsaXR5IFRlc3RzCgpgYGB7cn0KIyBIMDogbm8gc2Vhc29uYWxpdHkuIEJ5IGRlZmF1bHQgdGhlIHRlc3RzIHVzZSB0aGUgZmlyc3QtZGlmZmVyZW5jZWQgc2VyaWVzLAojIHdoaWNoIHJlbW92ZXMgdGhlIHRyZW5kLgprdyh5KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgS3J1c2thbC1XYWxsaXMgdGVzdApmcmllZCh5KSAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRnJpZWRtYW4gdGVzdApxcyh5KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUVMgdGVzdCAoYXV0b2NvcnJlbGF0aW9uIGF0IHNlYXNvbmFsIGxhZ3MpCmlzU2Vhc29uYWwoeSwgdGVzdCA9ICJjb21iaW5lZCIpICAgIyBXZWJlbC1PbGxlY2ggY29tYmluZWQgdGVzdDogVFJVRSA9IHNlYXNvbmFsCmBgYAoKIyMjIFRyZW5kIFRlc3RzCgpgYGB7cn0KIyBIMDogbm8gbW9ub3RvbmljIHRyZW5kCnNtay50ZXN0KHkpICAgIyBTZWFzb25hbCBNYW5uLUtlbmRhbGwgKGFjY291bnRzIGZvciBzZWFzb25hbGl0eSkKbWsudGVzdCh5KSAgICAjIE9yZGluYXJ5IE1hbm4tS2VuZGFsbCwgZm9yIGNvbXBhcmlzb24KCiMgU2Vhc29uYWwgU2VuJ3Mgc2xvcGUgPSBjaGFuZ2UgcGVyIHllYXI7IG5lZWRzIGNvbXBsZXRlIHllYXJzLCBzbyBKYW4gMjAxOCBpcyBkcm9wcGVkCnNlYS5zZW5zLnNsb3BlKHdpbmRvdyh5LCBlbmQgPSBjKDIwMTcsIDEyKSkpCmBgYAoKCiMjIFNpbXVsYXRpbmcgTWlzc2luZyBWYWx1ZXMKCmBgYHtyfQppbmplY3RfZ2FwcyA8LSBmdW5jdGlvbih4LCBwcm9wID0gMC4xNSwgZ2FwX2xlbiA9IDE6NDAsIGdhcF9wcm9iID0gMSAvIGdhcF9sZW4sCiAgICAgICAgICAgICAgICAgICAgICAgIGNvdmVyID0gbGlzdChzaW5nbGUgPSAxLCBzaG9ydCA9IDI6NSwgbG9uZyA9IDY6MTIpLAogICAgICAgICAgICAgICAgICAgICAgICBtaW5fc2VwID0gMSwgYnVmZmVyID0gMSkgewogIG4gPC0gbGVuZ3RoKHgpCiAgbSA8LSByb3VuZChwcm9wICogbikKCiAgIyAxLiBHYXAgbGVuZ3Roczogb25lIHBlciBjbGFzcyBpbiBgY292ZXJgLCB0aGUgcmVzdCByYW5kb20sIHN1bW1pbmcgdG8gbQogIEwgPC0gdmFwcGx5KGNvdmVyLCBmdW5jdGlvbihyKSByW3NhbXBsZS5pbnQobGVuZ3RoKHIpLCAxKV0sIG51bWVyaWMoMSkpCiAgaWYgKHN1bShMKSA+IG0pIHN0b3AoInByb3AgaXMgdG9vIHNtYWxsIGZvciB0aGUgZ2FwIGNsYXNzZXMgaW4gYGNvdmVyYC4iKQogIEwgPC0gYyhMLCBnYXBfbGVuW3NhbXBsZS5pbnQobGVuZ3RoKGdhcF9sZW4pLCBtLCByZXBsYWNlID0gVFJVRSwgcHJvYiA9IGdhcF9wcm9iKV0pCiAgayA8LSB3aGljaChjdW1zdW0oTCkgPj0gbSlbMV0KICBMIDwtIExbc2VxX2xlbihrKV0KICBMW2tdIDwtIG0gLSBzdW0oTFsta10pCiAgTCA8LSB1bm5hbWUoTFtzYW1wbGUuaW50KGspXSkKCiAgIyAyLiBPYnNlcnZlZCBwb2ludHMgdGhhdCBhcmUgZnJlZSB0byBiZSBwbGFjZWQgYW55d2hlcmUKICBmcmVlIDwtIG4gLSBtIC0gMiAqIGJ1ZmZlciAtIChrIC0gMSkgKiBtaW5fc2VwCiAgaWYgKGZyZWUgPCAwKSBzdG9wKCJHYXBzIGRvIG5vdCBmaXQ6IHJlZHVjZSBwcm9wLCBtaW5fc2VwIG9yIGJ1ZmZlci4iKQoKICAjIDMuIFNwcmVhZCB0aGUgZnJlZSBwb2ludHMgcmFuZG9tbHkgb3ZlciB0aGUgayArIDEgc3BhY2VzIChzdGFycyBhbmQgYmFycykKICBiYXJzICA8LSBzb3J0KHNhbXBsZS5pbnQoZnJlZSArIGssIGspKQogIGV4dHJhIDwtIGRpZmYoYygwLCBiYXJzLCBmcmVlICsgayArIDEpKSAtIDEKCiAgIyA0LiBHYXAgc3RhcnQgcG9zaXRpb25zLCB0aGVuIHNldCB0aGUgZ2FwcyB0byBOQQogIHNwYWNlIDwtIGMoYnVmZmVyLCByZXAobWluX3NlcCwgayAtIDEpKSArIGV4dHJhW3NlcV9sZW4oayldCiAgc3RhcnQgPC0gY3Vtc3VtKHNwYWNlICsgYygwLCBMWy1rXSkpICsgMQogIHhbc2VxdWVuY2UoTCwgZnJvbSA9IHN0YXJ0KV0gPC0gTkEKICB4Cn0KYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMjAyNikgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApkZl9taXNzaW5nIDwtIGRmICU+JQogIG11dGF0ZShJUEcyMjExQTJOID0gaW5qZWN0X2dhcHMoSVBHMjIxMUEyTiwgcHJvcCA9IDAuMjApKQoKZGZfbWlzc2luZwpgYGAKCgoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTQuNX0KIyBMZWZ0OiBzZXJpZXMgd2l0aCBtaXNzaW5nIHJlZ2lvbnMgKHggPSBkYXRlKQpwX3NlcmllcyA8LSBnZ3Bsb3RfbmFfZGlzdHJpYnV0aW9uKGRmX21pc3NpbmckSVBHMjIxMUEyTiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4X2F4aXNfbGFiZWxzID0gZGZfbWlzc2luZyREQVRFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhsYWIgPSAiWWVhciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWxhYiA9ICJQcm9kdWN0aW9uIGluZGV4IikgKwogIHNjYWxlX3hfZGF0ZSgKICAgIGJyZWFrcyA9IHNlcShhcy5EYXRlKCIxOTg1LTAxLTAxIiksIGFzLkRhdGUoIjIwMTUtMDEtMDEiKSwgYnkgPSAiNSB5ZWFycyIpLAogICAgZGF0ZV9sYWJlbHMgPSAiJVkiCiAgKQoKIyBSaWdodDogc2hhcmUgb2YgTkEgcGVyIHllYXIgKHggPSBvYnNlcnZhdGlvbiBpbmRleCwgcmVsYWJlbGxlZCBhcyB5ZWFycykKcF9pbnRlcnZhbCA8LSBzdXBwcmVzc01lc3NhZ2VzKAogIGdncGxvdF9uYV9kaXN0cmlidXRpb24yKGRmX21pc3NpbmckSVBHMjIxMUEyTiwgaW50ZXJ2YWxfc2l6ZSA9IDEyLCB4bGFiID0gIlllYXIiKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDM2MCwgYnkgPSA2MCksICAgICAjIGluZGV4IDAsIDYwLCAuLi4gPSBKYW4gMTk4NSwgSmFuIDE5OTAsIC4uLgogICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgxOTg1LCAyMDE1LCBieSA9IDUpLAogICAgICAgICAgICAgICAgICAgICAgIGV4cGFuZCA9IGMoMCwgMCkpCikKCnBfc2VyaWVzIHwgcF9pbnRlcnZhbApgYGAKCiMjIEltcHV0YXRpb24KCmBgYHtyfQp5X25hIDwtIHRzKGRmX21pc3NpbmckSVBHMjIxMUEyTiwgc3RhcnQgPSBjKDE5ODUsIDEpLCBmcmVxdWVuY3kgPSAxMikKCnF1aWV0X3N0cnVjdHRzIDwtIGZ1bmN0aW9uKGV4cHIpIHsKICB3aXRoQ2FsbGluZ0hhbmRsZXJzKGV4cHIsIHdhcm5pbmcgPSBmdW5jdGlvbih3KSB7CiAgICBpZiAoZ3JlcGwoInBvc3NpYmxlIGNvbnZlcmdlbmNlIHByb2JsZW0iLCBjb25kaXRpb25NZXNzYWdlKHcpKSkgaW52b2tlUmVzdGFydCgibXVmZmxlV2FybmluZyIpCiAgfSkKfQoKc2V0LnNlZWQoMjAyNikgICAjIG5hX3JhbmRvbSgpIGFuZCB0aGUgInJhbmRvbSIgYWxnb3JpdGhtcyBkcmF3IHJhbmRvbSB2YWx1ZXMKaW1wdXRhdGlvbnMgPC0gbGlzdCgKICAjIE1lYW4tYmFzZWQKICBtZWFuICAgICAgPSBuYV9tZWFuKHlfbmEsIG9wdGlvbiA9ICJtZWFuIiksCiAgbWVkaWFuICAgID0gbmFfbWVhbih5X25hLCBvcHRpb24gPSAibWVkaWFuIiksCiAgbW9kZSAgICAgID0gbmFfbWVhbih5X25hLCBvcHRpb24gPSAibW9kZSIpLAogIGhhcm1vbmljICA9IG5hX21lYW4oeV9uYSwgb3B0aW9uID0gImhhcm1vbmljIiksCiAgZ2VvbWV0cmljID0gbmFfbWVhbih5X25hLCBvcHRpb24gPSAiZ2VvbWV0cmljIiksCgogICMgUmFuZG9tIHZhbHVlcyBiZXR3ZWVuIHRoZSBvYnNlcnZlZCBtaW5pbXVtIGFuZCBtYXhpbXVtCiAgcmFuZG9tID0gbmFfcmFuZG9tKHlfbmEpLAoKICAjIExhc3Qgb2JzZXJ2YXRpb24gY2FycmllZCBmb3J3YXJkIC8gbmV4dCBvYnNlcnZhdGlvbiBjYXJyaWVkIGJhY2t3YXJkCiAgbG9jZiA9IG5hX2xvY2YoeV9uYSwgb3B0aW9uID0gImxvY2YiKSwKICBub2NiID0gbmFfbG9jZih5X25hLCBvcHRpb24gPSAibm9jYiIpLAoKICAjIEludGVycG9sYXRpb24KICBsaW5lYXIgPSBuYV9pbnRlcnBvbGF0aW9uKHlfbmEsIG9wdGlvbiA9ICJsaW5lYXIiKSwKICBzcGxpbmUgPSBuYV9pbnRlcnBvbGF0aW9uKHlfbmEsIG9wdGlvbiA9ICJzcGxpbmUiKSwKICBzdGluZSAgPSBuYV9pbnRlcnBvbGF0aW9uKHlfbmEsIG9wdGlvbiA9ICJzdGluZSIpLAoKICAjIFdlaWdodGVkIG1vdmluZyBhdmVyYWdlIChrID0gNCBvYnNlcnZhdGlvbnMgb24gZWFjaCBzaWRlKQogIG1hX3NpbXBsZSAgICAgID0gbmFfbWEoeV9uYSwgayA9IDQsIHdlaWdodGluZyA9ICJzaW1wbGUiKSwKICBtYV9saW5lYXIgICAgICA9IG5hX21hKHlfbmEsIGsgPSA0LCB3ZWlnaHRpbmcgPSAibGluZWFyIiksCiAgbWFfZXhwb25lbnRpYWwgPSBuYV9tYSh5X25hLCBrID0gNCwgd2VpZ2h0aW5nID0gImV4cG9uZW50aWFsIiksCgogICMgS2FsbWFuIHNtb290aGluZwogIGthbG1hbl9zdHJ1Y3R0cyA9IHF1aWV0X3N0cnVjdHRzKG5hX2thbG1hbih5X25hLCBtb2RlbCA9ICJTdHJ1Y3RUUyIpKSwKICBrYWxtYW5fYXJpbWEgICAgPSBuYV9rYWxtYW4oeV9uYSwgbW9kZWwgPSAiYXV0by5hcmltYSIpLAoKICAjIFNlYXNvbmFsIGRlY29tcG9zaXRpb246IGltcHV0ZSB0aGUgZGVzZWFzb25hbGlzZWQgc2VyaWVzLCB0aGVuIGFkZCB0aGUgc2Vhc29uIGJhY2sKICBzZWFkZWNfaW50ZXJwb2xhdGlvbiA9IG5hX3NlYWRlYyh5X25hLCBhbGdvcml0aG0gPSAiaW50ZXJwb2xhdGlvbiIpLAogIHNlYWRlY19sb2NmICAgICAgICAgID0gbmFfc2VhZGVjKHlfbmEsIGFsZ29yaXRobSA9ICJsb2NmIiksCiAgc2VhZGVjX21lYW4gICAgICAgICAgPSBuYV9zZWFkZWMoeV9uYSwgYWxnb3JpdGhtID0gIm1lYW4iKSwKICBzZWFkZWNfcmFuZG9tICAgICAgICA9IG5hX3NlYWRlYyh5X25hLCBhbGdvcml0aG0gPSAicmFuZG9tIiksCiAgc2VhZGVjX2thbG1hbiAgICAgICAgPSBxdWlldF9zdHJ1Y3R0cyhuYV9zZWFkZWMoeV9uYSwgYWxnb3JpdGhtID0gImthbG1hbiIpKSwKICBzZWFkZWNfbWEgICAgICAgICAgICA9IG5hX3NlYWRlYyh5X25hLCBhbGdvcml0aG0gPSAibWEiKSwKCiAgIyBTZWFzb25hbCBzcGxpdDogaW1wdXRlIGVhY2ggY2FsZW5kYXIgbW9udGgncyBzdWJzZXJpZXMgc2VwYXJhdGVseQogIHNlYXNwbGl0X2ludGVycG9sYXRpb24gPSBuYV9zZWFzcGxpdCh5X25hLCBhbGdvcml0aG0gPSAiaW50ZXJwb2xhdGlvbiIpLAogIHNlYXNwbGl0X2xvY2YgICAgICAgICAgPSBuYV9zZWFzcGxpdCh5X25hLCBhbGdvcml0aG0gPSAibG9jZiIpLAogIHNlYXNwbGl0X21lYW4gICAgICAgICAgPSBuYV9zZWFzcGxpdCh5X25hLCBhbGdvcml0aG0gPSAibWVhbiIpLAogIHNlYXNwbGl0X3JhbmRvbSAgICAgICAgPSBuYV9zZWFzcGxpdCh5X25hLCBhbGdvcml0aG0gPSAicmFuZG9tIiksCiAgc2Vhc3BsaXRfa2FsbWFuICAgICAgICA9IHF1aWV0X3N0cnVjdHRzKG5hX3NlYXNwbGl0KHlfbmEsIGFsZ29yaXRobSA9ICJrYWxtYW4iKSksCiAgc2Vhc3BsaXRfbWEgICAgICAgICAgICA9IG5hX3NlYXNwbGl0KHlfbmEsIGFsZ29yaXRobSA9ICJtYSIpCikKYGBgCgpgYGB7cn0KZGZfaW1wdXRlZCA8LSBkZl9taXNzaW5nICU+JQogIHNlbGVjdChEQVRFLCBvYnNlcnZlZCA9IElQRzIyMTFBMk4pICU+JQogIG11dGF0ZShhY3R1YWwgPSBkZiRJUEcyMjExQTJOLCAuYWZ0ZXIgPSBEQVRFKSAlPiUKICBiaW5kX2NvbHMoYXNfdGliYmxlKG1hcChpbXB1dGF0aW9ucywgYXMubnVtZXJpYykpKQoKZGZfaW1wdXRlZApgYGAKCgojIyBFdmFsdWF0aW9uCgojIyMgUk1TRSBhbmQgTUFQRQoKQWNjdXJhY3kgaXMgY29tcHV0ZWQgb25seSBvbiB0aGUgJG0kIGltcHV0ZWQgKG9yaWdpbmFsbHkgbWlzc2luZykgcG9pbnRzICRcbWF0aGNhbHtNfSQ6CgokJApcdGV4dHtSTVNFfSA9IFxzcXJ0e1xmcmFjezF9e219XHN1bV97dCBcaW4gXG1hdGhjYWx7TX19IChcaGF0e3l9X3QgLSB5X3QpXjJ9LCBccXF1YWQKXHRleHR7TUFQRX0gPSBcZnJhY3sxMDB9e219XHN1bV97dCBcaW4gXG1hdGhjYWx7TX19IFxsZWZ0XGx2ZXJ0IFxmcmFje1xoYXR7eX1fdCAtIHlfdH17eV90fSBccmlnaHRccnZlcnQKJCQKCgoKYGBge3Igcm93cy5wcmludD0yOH0KbWV0aG9kX2dyb3VwcyA8LSBsaXN0KAogICJNZWFuLWJhc2VkIiAgICAgICAgICAgICA9IGMoIm1lYW4iLCAibWVkaWFuIiwgIm1vZGUiLCAiaGFybW9uaWMiLCAiZ2VvbWV0cmljIiksCiAgIlJhbmRvbSIgICAgICAgICAgICAgICAgID0gInJhbmRvbSIsCiAgIkxPQ0YgLyBOT0NCIiAgICAgICAgICAgID0gYygibG9jZiIsICJub2NiIiksCiAgIkludGVycG9sYXRpb24iICAgICAgICAgID0gYygibGluZWFyIiwgInNwbGluZSIsICJzdGluZSIpLAogICJNb3ZpbmcgQXZlcmFnZSIgICAgICAgICA9IGMoIm1hX3NpbXBsZSIsICJtYV9saW5lYXIiLCAibWFfZXhwb25lbnRpYWwiKSwKICAiS2FsbWFuIFNtb290aGluZyIgICAgICAgPSBjKCJrYWxtYW5fc3RydWN0dHMiLCAia2FsbWFuX2FyaW1hIiksCiAgIlNlYXNvbmFsIERlY29tcG9zaXRpb24iID0gcGFzdGUwKCJzZWFkZWNfIiwgICBjKCJpbnRlcnBvbGF0aW9uIiwgImxvY2YiLCAibWVhbiIsICJyYW5kb20iLCAia2FsbWFuIiwgIm1hIikpLAogICJTZWFzb25hbCBTcGxpdCIgICAgICAgICA9IHBhc3RlMCgic2Vhc3BsaXRfIiwgYygiaW50ZXJwb2xhdGlvbiIsICJsb2NmIiwgIm1lYW4iLCAicmFuZG9tIiwgImthbG1hbiIsICJtYSIpKQopCgojIFJNU0UgYW5kIE1BUEUgb24gdGhlIGltcHV0ZWQgcG9pbnRzIG9ubHkKZXZhbHVhdGlvbiA8LSBkZl9pbXB1dGVkICU+JQogIGZpbHRlcihpcy5uYShvYnNlcnZlZCkpICU+JQogIHBpdm90X2xvbmdlcigtYyhEQVRFLCBhY3R1YWwsIG9ic2VydmVkKSwgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gImltcHV0ZWQiKSAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIHN1bW1hcmlzZShSTVNFID0gc3FydChtZWFuKChpbXB1dGVkIC0gYWN0dWFsKV4yKSksCiAgICAgICAgICAgIE1BUEUgPSAxMDAgKiBtZWFuKGFicyhpbXB1dGVkIC0gYWN0dWFsKSAvIGFjdHVhbCkpICU+JQogIGxlZnRfam9pbihlbmZyYW1lKG1ldGhvZF9ncm91cHMsIG5hbWUgPSAiZ3JvdXAiLCB2YWx1ZSA9ICJtZXRob2QiKSAlPiUgdW5uZXN0KG1ldGhvZCksCiAgICAgICAgICAgIGJ5ID0gIm1ldGhvZCIpICU+JQogIHJlbG9jYXRlKGdyb3VwKSAlPiUKICBhcnJhbmdlKFJNU0UpCgojIERpc3BsYXk6IHJhbmtlZCB0YWJsZSwgcm91bmRlZCwgYWxsIDI4IHJvd3Mgb24gb25lIHBhZ2UgKGNodW5rIG9wdGlvbiByb3dzLnByaW50KQpldmFsdWF0aW9uICU+JQogIG11dGF0ZShSYW5rID0gcm93X251bWJlcigpLCBhY3Jvc3MoYyhSTVNFLCBNQVBFKSwgfiByb3VuZCgueCwgMykpKSAlPiUKICBzZWxlY3QoUmFuaywgRmFtaWx5ID0gZ3JvdXAsIE1ldGhvZCA9IG1ldGhvZCwgUk1TRSwgYE1BUEUgKCUpYCA9IE1BUEUpCmBgYAoKCiMjIyBJbXB1dGVkIFRpbWUgU2VyaWVzIFZpc3VhbGl6YXRpb24KCmBgYHtyfQpwbG90X2ltcHV0YXRpb25zIDwtIGZ1bmN0aW9uKGdyb3VwLCBuY29sID0gMikgewogIG1ldGhvZHMgPC0gbWV0aG9kX2dyb3Vwc1tbZ3JvdXBdXQogIGxhYmVscyAgPC0gZXZhbHVhdGlvbiAlPiUKICAgIG11dGF0ZShsYWJlbCA9IHNwcmludGYoIiVzICAoUk1TRSAlLjJmLCBNQVBFICUuMmYlJSkiLCBtZXRob2QsIFJNU0UsIE1BUEUpKQoKICBkZl9pbXB1dGVkICU+JQogICAgc2VsZWN0KERBVEUsIGFjdHVhbCwgb2JzZXJ2ZWQsIGFsbF9vZihtZXRob2RzKSkgJT4lCiAgICBwaXZvdF9sb25nZXIoYWxsX29mKG1ldGhvZHMpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiaW1wdXRlZCIpICU+JQogICAgbXV0YXRlKG1ldGhvZCAgPSBmYWN0b3IobWV0aG9kLCBsZXZlbHMgPSBtZXRob2RzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gbGFiZWxzJGxhYmVsW21hdGNoKG1ldGhvZHMsIGxhYmVscyRtZXRob2QpXSksCiAgICAgICAgICAgaW1wdXRlZCA9IGlmX2Vsc2UoaXMubmEob2JzZXJ2ZWQpLCBpbXB1dGVkLCBOQV9yZWFsXykpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gREFURSkpICsKICAgIGdlb21fbGluZShhZXMoeSA9IGFjdHVhbCksIGNvbG91ciA9ICJncmV5NzAiLCBsaW5ld2lkdGggPSAwLjMpICsKICAgIGdlb21fbGluZShhZXMoeSA9IG9ic2VydmVkKSwgY29sb3VyID0gInN0ZWVsYmx1ZTQiLCBsaW5ld2lkdGggPSAwLjQsIG5hLnJtID0gVFJVRSkgKwogICAgZ2VvbV9wb2ludChhZXMoeSA9IGltcHV0ZWQpLCBjb2xvdXIgPSAiZmlyZWJyaWNrIiwgc2l6ZSA9IDAuOCwgbmEucm0gPSBUUlVFKSArCiAgICBmYWNldF93cmFwKH4gbWV0aG9kLCBuY29sID0gbmNvbCkgKwogICAgc2NhbGVfeF9kYXRlKAogICAgICBicmVha3MgPSBzZXEoYXMuRGF0ZSgiMTk4NS0wMS0wMSIpLCBhcy5EYXRlKCIyMDE1LTAxLTAxIiksIGJ5ID0gIjUgeWVhcnMiKSwKICAgICAgZGF0ZV9sYWJlbHMgPSAiJVkiCiAgICApICsKICAgIGxhYnModGl0bGUgICAgPSBncm91cCwKICAgICAgICAgc3VidGl0bGUgPSAiR3JleSA9IGFjdHVhbCAoaGlkZGVuKSB2YWx1ZXMsIGJsdWUgPSBvYnNlcnZlZCwgcmVkID0gaW1wdXRlZCIsCiAgICAgICAgIHggPSAiWWVhciIsIHkgPSAiUHJvZHVjdGlvbiBpbmRleCIpICsKICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTEpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpCn0KYGBgCgojIyMjIE1lYW4tYmFzZWQKCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04LjV9CnBsb3RfaW1wdXRhdGlvbnMoIk1lYW4tYmFzZWQiKQpgYGAKCiMjIyMgUmFuZG9tCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9My41fQpwbG90X2ltcHV0YXRpb25zKCJSYW5kb20iLCBuY29sID0gMSkKYGBgCgojIyMjIExPQ0YgLyBOT0NCCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9My41fQpwbG90X2ltcHV0YXRpb25zKCJMT0NGIC8gTk9DQiIpCmBgYAoKIyMjIyBJbnRlcnBvbGF0aW9uCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9Nn0KcGxvdF9pbXB1dGF0aW9ucygiSW50ZXJwb2xhdGlvbiIpCmBgYAoKIyMjIyBNb3ZpbmcgQXZlcmFnZQoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9CnBsb3RfaW1wdXRhdGlvbnMoIk1vdmluZyBBdmVyYWdlIikKYGBgCgojIyMjIEthbG1hbiBTbW9vdGhpbmcKCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0zLjV9CnBsb3RfaW1wdXRhdGlvbnMoIkthbG1hbiBTbW9vdGhpbmciKQpgYGAKCiMjIyBTZWFzb25hbCBEZWNvbXBvc2l0aW9uCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OC41fQpwbG90X2ltcHV0YXRpb25zKCJTZWFzb25hbCBEZWNvbXBvc2l0aW9uIikKYGBgCgojIyMjIFNlYXNvbmFsIFNwbGl0CgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OC41fQpwbG90X2ltcHV0YXRpb25zKCJTZWFzb25hbCBTcGxpdCIpCmBgYAoK