Note that the directories used to store data are likely different on your computer, and such references will need to be changed before using any such code.
library(knitr)
library(kableExtra)
html_df <- function(text, cols=NULL, col1=FALSE, full=F) {
if(!length(cols)) {
cols=colnames(text)
}
if(!col1) {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full)
} else {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full) %>%
column_spec(1,bold=T)
}
}
library(tidyverse)
weekly <- read.csv("../../Data/WMT_train.csv")
weekly.test <- read.csv("../../Data/WMT_test.csv")
weekly.features <- read.csv("../../Data/WMT_features.csv")
weekly.stores <- read.csv("../../Data/WMT_stores.csv")
wmae <- function(actual, predicted, holidays) {
sum(abs(actual-predicted)*(holidays*4+1)) / (length(actual) + 4*sum(holidays))
}
library(tidyverse) # we'll extensively use dplyr here
library(lubridate) # Great for simple date functions
library(broom)
weekly <- read.csv("../../Data/WMT_train.csv", stringsAsFactors=FALSE)
weekly.test <- read.csv("../../Data/WMT_test.csv", stringsAsFactors=FALSE)
weekly.features <- read.csv("../../Data/WMT_features.csv", stringsAsFactors=FALSE)
weekly.stores <- read.csv("../../Data/WMT_stores.csv", stringsAsFactors=FALSE)
preprocess_data <- function(df) {
# Merge the data together (Pulled from outside of function -- "scoping")
df <- inner_join(df, weekly.stores)
df <- inner_join(df, weekly.features[,1:11])
# Compress the weird markdown information to 1 variable
df$markdown <- 0
df[!is.na(df$MarkDown1),]$markdown <- df[!is.na(df$MarkDown1),]$MarkDown1
df[!is.na(df$MarkDown2),]$markdown <- df[!is.na(df$MarkDown2),]$MarkDown2
df[!is.na(df$MarkDown3),]$markdown <- df[!is.na(df$MarkDown3),]$MarkDown3
df[!is.na(df$MarkDown4),]$markdown <- df[!is.na(df$MarkDown4),]$MarkDown4
df[!is.na(df$MarkDown5),]$markdown <- df[!is.na(df$MarkDown5),]$MarkDown5
# Fix dates and add useful time variables
df$date <- as.Date(df$Date)
df$week <- week(df$date)
df$year <- year(df$date)
df
}
df <- preprocess_data(weekly)
Joining, by = "Store"
Joining, by = c("Store", "Date")
df_test <- preprocess_data(weekly.test)
Joining, by = "Store"
Joining, by = c("Store", "Date")
df[91:94,] %>%
select(Store, date, markdown, MarkDown3, MarkDown4, MarkDown5) %>%
html_df()
|
Store |
date |
markdown |
MarkDown3 |
MarkDown4 |
MarkDown5 |
91 |
1 |
2011-10-28 |
0.00 |
NA |
NA |
NA |
92 |
1 |
2011-11-04 |
0.00 |
NA |
NA |
NA |
93 |
1 |
2011-11-11 |
6551.42 |
215.07 |
2406.62 |
6551.42 |
94 |
1 |
2011-11-18 |
5988.57 |
51.98 |
427.39 |
5988.57 |
df[1:2,] %>% select(date, week, year) %>% html_df()
date |
week |
year |
2010-02-05 |
6 |
2010 |
2010-02-12 |
7 |
2010 |
# Fill in missing CPI and Unemployment data
df_test <- df_test %>%
group_by(Store, year) %>%
mutate(CPI=ifelse(is.na(CPI), mean(CPI,na.rm=T), CPI),
Unemployment=ifelse(is.na(Unemployment),
mean(Unemployment,na.rm=T),
Unemployment)) %>%
ungroup()
# Unique IDs in the data
df$id <- df$Store *10000 + df$week * 100 + df$Dept
df_test$id <- df_test$Store *10000 + df_test$week * 100 + df_test$Dept
# Unique ID and factor building
swd <- c(df$id, df_test$id) # Pool all IDs
swd <- unique(swd) # Only keep unique elements
swd <- data.frame(id=swd) # Make a data frame
swd$swd <- factor(swd$id) # Extract factors for using later
# Add unique factors to data -- ensures same factors for both data sets
df <- left_join(df,swd)
Joining, by = "id"
df_test <- left_join(df_test,swd)
Joining, by = "id"
df_test$Id <- paste0(df_test$Store,'_',df_test$Dept,"_",df_test$date)
html_df(df_test[c(20000,40000,60000),c("Store","week","Dept","id","swd","Id")])
Store |
week |
Dept |
id |
swd |
Id |
8 |
27 |
33 |
82733 |
82733 |
8_33_2013-07-05 |
15 |
46 |
91 |
154691 |
154691 |
15_91_2012-11-16 |
23 |
52 |
25 |
235225 |
235225 |
23_25_2012-12-28 |
# Calculate average by store-dept and distribute to df_test
df <- df %>%
group_by(Store, Dept) %>%
mutate(store_avg=mean(Weekly_Sales, rm.na=T)) %>%
ungroup()
df_sa <- df %>%
group_by(Store, Dept) %>%
slice(1) %>%
select(Store, Dept, store_avg) %>%
ungroup()
df_test <- left_join(df_test, df_sa)
Joining, by = c("Store", "Dept")
# 36 observations have messed up department codes -- ignore (set to 0)
df_test[is.na(df_test$store_avg),]$store_avg <- 0
# Calculate multipliers based on store_avg (and removing NaN and Inf)
df$Weekly_mult <- df$Weekly_Sales / df$store_avg
df[!is.finite(df$Weekly_mult),]$Weekly_mult <- NA
# Calculate mean by week-store-dept and distribute to df_test
df <- df %>%
group_by(Store, Dept, week) %>%
mutate(naive_mean=mean(Weekly_Sales, rm.na=T)) %>%
ungroup()
df_wm <- df %>%
group_by(Store, Dept, week) %>%
slice(1) %>%
ungroup() %>%
select(Store, Dept, week, naive_mean)
df_test <- df_test %>% arrange(Store, Dept, week)
df_test <- left_join(df_test, df_wm)
Joining, by = c("Store", "Dept", "week")
table(is.na(df_test$naive_mean))
FALSE TRUE
113827 1237
# there are a lot of scattered missing weeks -- fill with a lag first
df_test$naive_na <- is.na(df_test$naive_mean)
df_test <- df_test %>%
arrange(Store, Dept, date) %>%
group_by(Store, Dept) %>%
mutate(naive_mean=ifelse(is.na(naive_mean), lag(naive_mean),naive_mean)) %>%
ungroup()
df_test <- df_test %>%
arrange(Store, Dept, date) %>%
group_by(Store, Dept) %>%
mutate(naive_mean=ifelse(is.na(naive_mean), lag(naive_mean,2),naive_mean)) %>%
ungroup()
df_test <- df_test %>%
arrange(Store, Dept, date) %>%
group_by(Store, Dept) %>%
mutate(naive_mean=ifelse(is.na(naive_mean), lead(naive_mean,1),naive_mean)) %>%
ungroup()
df_test <- df_test %>%
arrange(Store, Dept, date) %>%
group_by(Store, Dept) %>%
mutate(naive_mean=ifelse(is.na(naive_mean), lead(naive_mean,2),naive_mean)) %>%
ungroup()
df_test$naive_mean <- ifelse(is.na(df_test$naive_mean), df_test$store_avg, df_test$naive_mean)
df %>%
group_by(week, Store) %>%
mutate(sales=mean(Weekly_Sales)) %>%
slice(1) %>%
ungroup() %>%
ggplot(aes(y=sales, x=week, color=factor(Store))) +
geom_line() + xlab("Week") + ylab("Sales for Store (dept average)") +
theme(legend.position="none")

mod1 <- lm(Weekly_mult ~ factor(IsHoliday) + factor(markdown>0) +
markdown + Temperature +
Fuel_Price + CPI + Unemployment,
data=df)
tidy(mod1)
glance(mod1)
# Out of sample result
df_test$Weekly_mult <- predict(mod1, df_test)
df_test$Weekly_Sales <- df_test$Weekly_mult * df_test$store_avg
# Required to submit a csv of Id and Weekly_Sales
write.csv(df_test[,c("Id","Weekly_Sales")],
"WMT_linear.csv",
row.names=FALSE)
# track
df_test$WS_linear <- df_test$Weekly_Sales
# Check in sample WMAE
df$WS_linear <- predict(mod1, df) * df$store_avg
w <- wmae(actual=df$Weekly_Sales, predicted=df$WS_linear, holidays=df$IsHoliday)
names(w) <- "Linear"
wmaes <- c(w)
wmaes
Linear
3073.57
wmae_obs <- function(actual, predicted, holidays) {
abs(actual-predicted)*(holidays*5+1) / (length(actual) + 4*sum(holidays))
}
df$wmaes <- wmae_obs(actual=df$Weekly_Sales, predicted=df$WS_linear,
holidays=df$IsHoliday)
ggplot(data=df, aes(y=wmaes, x=week, color=factor(IsHoliday))) +
geom_jitter(width=0.25) + xlab("Week") + ylab("WMAE")

mod2 <- lm(Weekly_mult ~ factor(week) + factor(IsHoliday) + factor(markdown>0) +
markdown + Temperature +
Fuel_Price + CPI + Unemployment,
data=df)
tidy(mod2)
glance(mod2)
# Out of sample result
df_test$Weekly_mult <- predict(mod2, df_test)
df_test$Weekly_Sales <- df_test$Weekly_mult * df_test$store_avg
# Required to submit a csv of Id and Weekly_Sales
write.csv(df_test[,c("Id","Weekly_Sales")],
"WMT_linear2.csv",
row.names=FALSE)
# track
df_test$WS_linear2 <- df_test$Weekly_Sales
# Check in sample WMAE
df$WS_linear2 <- predict(mod2, df) * df$store_avg
w <- wmae(actual=df$Weekly_Sales, predicted=df$WS_linear2, holidays=df$IsHoliday)
names(w) <- "Linear 2"
wmaes <- c(wmaes, w)
wmaes
Linear Linear 2
3073.570 3230.643
wmaes_out <- c(4993.4, 5618.4)
names(wmaes_out) <- c("Linear", "Linear 2")
wmaes_out
Linear Linear 2
4993.4 5618.4
df$wmaes <- wmae_obs(actual=df$Weekly_Sales, predicted=df$WS_linear2,
holidays=df$IsHoliday)
ggplot(data=df, aes(y=wmaes,
x=week,
color=factor(IsHoliday))) +
geom_jitter(width=0.25) + xlab("Week") + ylab("WMAE")

ggplot(data=df, aes(y=wmae_obs(actual=df$Weekly_Sales, predicted=df$WS_linear2,
holidays=df$IsHoliday),
x=week,
color=factor(Store))) +
geom_jitter(width=0.25) + xlab("Week") + ylab("WMAE") +
theme(legend.position="none")

ggplot(data=df, aes(y=wmae_obs(actual=df$Weekly_Sales, predicted=df$WS_linear2,
holidays=df$IsHoliday),
x=week,
color=factor(Dept))) +
geom_jitter(width=0.25) + xlab("Week") + ylab("WMAE") +
theme(legend.position="none")

library(lfe)
Loading required package: Matrix
Attaching package: 㤼㸱Matrix㤼㸲
The following object is masked from 㤼㸱package:tidyr㤼㸲:
expand
mod3 <- felm(Weekly_mult ~ markdown +
Temperature +
Fuel_Price +
CPI +
Unemployment | swd, data=df)
tidy(mod3)
glance(mod3)
predict.felm <- function(object, newdata, use.fe=T, ...) {
# compatible with tibbles
newdata <- as.data.frame(newdata)
co <- coef(object)
y.pred <- t(as.matrix(unname(co))) %*% t(as.matrix(newdata[,names(co)]))
fe.vars <- names(object$fe)
all.fe <- getfe(object)
for (fe.var in fe.vars) {
level <- all.fe[all.fe$fe == fe.var,]
frows <- match(newdata[[fe.var]],level$idx)
myfe <- level$effect[frows]
myfe[is.na(myfe)] = 0
y.pred <- y.pred + myfe
}
as.vector(y.pred)
}
# Out of sample result
df_test$Weekly_mult <- predict(mod3, df_test)
df_test$Weekly_Sales <- df_test$Weekly_mult * df_test$store_avg
# Required to submit a csv of Id and Weekly_Sales
write.csv(df_test[,c("Id","Weekly_Sales")],
"WMT_FE.csv",
row.names=FALSE)
# track
df_test$WS_FE <- df_test$Weekly_Sales
# Check in sample WMAE
df$WS_FE <- predict(mod3, df) * df$store_avg
w <- wmae(actual=df$Weekly_Sales, predicted=df$WS_FE, holidays=df$IsHoliday)
names(w) <- "FE"
wmaes <- c(wmaes, w)
wmaes
Linear Linear 2 FE
3073.570 3230.643 1552.173
wmaes_out <- c(4993.4, 5618.4, 3378.8)
names(wmaes_out) <- c("Linear", "Linear 2", "FE")
wmaes_out
Linear Linear 2 FE
4993.4 5618.4 3378.8
df$wmaes <- wmae_obs(actual=df$Weekly_Sales, predicted=df$WS_FE,
holidays=df$IsHoliday)
ggplot(data=df, aes(y=wmaes,
x=week,
color=factor(IsHoliday))) +
geom_jitter(width=0.25) + xlab("Week") + ylab("WMAE")

# Function to map holidays
holidf <- function(df, years, weeks) {
if(length(years) == 2) {
years = c(years, 9999)
weeks = c(weeks, 99)
}
df %>%
filter(week(df$date) == weeks[1] & year(df$date) == years[1] |
week(df$date) == weeks[2] & year(df$date) == years[2] |
week(df$date) == weeks[3] & year(df$date) == years[3]) %>%
select(Store,Dept,Weekly_Sales) %>%
group_by(Store,Dept) %>%
mutate(holiday_sales=mean(Weekly_Sales,rm.na=T)) %>%
slice(1) %>%
ungroup() %>%
select(Store,Dept,holiday_sales) %>%
as.data.frame()
}
# Add holiday shift to data
add_holiday <- function(df, holiday, wk) {
q <- left_join(df, holiday)
q <- q %>% transmute(Weekly_Sales=ifelse(week==wk,holiday_sales,Weekly_Sales))
q[[1]]
}
# Start with our FE approach
df_test$Weekly_Sales <- df_test$WS_FE
# CHRISTMAS
# 2010: 53, 2011: 52, 2012: *52*
# Get a df with Christmas specific sales
s_xmas <- holidf(df, c(2010,2011), c(53,52))
s_xmas_m1 <- holidf(df, c(2010,2011), c(52,51))
df_test$Weekly_Sales <- add_holiday(df_test, s_xmas, 52)
Joining, by = c("Store", "Dept")
df_test$Weekly_Sales <- add_holiday(df_test, s_xmas_m1, 51)
Joining, by = c("Store", "Dept")
# BLACK FRIDAY 2010-11-26: 48; 2011-11-25: 47, 2012-11-23: *47*
s_bf <- holidf(df, c(2010,2011), c(48,47))
df_test$Weekly_Sales <- add_holiday(df_test, s_bf, 47)
Joining, by = c("Store", "Dept")
# Note that the superbowl is weeks 6, 6, 7, *6*
s_sb <- holidf(df, c(2010,2011,2012), c(6,6,7))
s_sb_m1 <- holidf(df, c(2010,2011,2012), c(5,5,6))
df_test$Weekly_Sales <- add_holiday(df_test, s_sb, 7)
Joining, by = c("Store", "Dept")
df_test$Weekly_Sales <- add_holiday(df_test, s_sb_m1, 6)
Joining, by = c("Store", "Dept")
# Clean up any missing values added
df_test[is.na(df_test$Weekly_Sales),]$Weekly_Sales <- df_test[is.na(df_test$Weekly_Sales),]$naive_mean
# Calculate yearly growt
# A bit difficult since the data is a partial year, a full year, and a partial year
yg1 <- df %>%
arrange(Store, year) %>%
filter(year == 2010) %>%
group_by(Store, year) %>%
mutate(sales2010=mean(Weekly_Sales)) %>%
slice(1) %>%
ungroup() %>%
select(Store, sales2010)
yg2 <- df %>%
arrange(Store, year) %>%
filter(date > as.Date("2011-02-05") & year == 2011) %>%
group_by(Store, year) %>%
mutate(sales2011a=mean(Weekly_Sales)) %>%
slice(1) %>%
ungroup() %>%
select(Store, sales2011a)
yg3 <- df %>%
arrange(Store, year) %>%
filter(date < as.Date("2011-10-06") & year == 2011) %>%
group_by(Store, year) %>%
mutate(sales2011b=mean(Weekly_Sales)) %>%
slice(1) %>%
ungroup() %>%
select(Store, sales2011b)
yg4 <- df %>%
arrange(Store, year) %>%
filter(year == 2012) %>%
group_by(Store, year) %>%
mutate(sales2012=mean(Weekly_Sales)) %>%
slice(1) %>%
ungroup() %>%
select(Store, sales2012)
yg <- full_join(yg1,yg2) %>% full_join(yg3) %>% full_join(yg4)
Joining, by = "Store"
Joining, by = "Store"
Joining, by = "Store"
yg <- yg %>%
mutate(growth1=sales2011a/sales2010,
growth2=sales2012/sales2011b,
growth=ifelse(is.na(growth1),ifelse(is.na(growth2),1,growth2),ifelse(is.na(growth2),1,(growth1+growth2)/2))) %>%
select(Store, growth)
# Add growth data to our df_test data frame
df_test <- left_join(df_test, yg)
Joining, by = "Store"
df_test[df_test$year == 2012,]$Weekly_Sales <- df_test[df_test$year == 2012,]$Weekly_Sales * df_test[df_test$year == 2012,]$growth
df_test[df_test$year == 2013,]$Weekly_Sales <- df_test[df_test$year == 2013,]$Weekly_Sales * df_test[df_test$year == 2013,]$growth
write.csv(df_test[,c("Id","Weekly_Sales")], file="WMT_FE_shift.csv", row.names=FALSE)
df_test$WS_FE_shift <- df_test$Weekly_Sales
# BONUS: Ensembling
# Ensemble: Building multiple models and combining them into 1
# This example: add in a naive mean approach with shifting + growth
df_test$Weekly_Sales <- df_test$naive_mean
df_test$Weekly_Sales <- add_holiday(df_test, s_xmas, 52)
Joining, by = c("Store", "Dept")
df_test$Weekly_Sales <- add_holiday(df_test, s_xmas_m1, 51)
Joining, by = c("Store", "Dept")
df_test$Weekly_Sales <- add_holiday(df_test, s_bf, 47)
Joining, by = c("Store", "Dept")
df_test$Weekly_Sales <- add_holiday(df_test, s_sb, 7)
Joining, by = c("Store", "Dept")
df_test$Weekly_Sales <- add_holiday(df_test, s_sb_m1, 6)
Joining, by = c("Store", "Dept")
df_test[is.na(df_test$Weekly_Sales),]$Weekly_Sales <- df_test[is.na(df_test$Weekly_Sales),]$naive_mean
df_test[df_test$year == 2012,]$Weekly_Sales <- df_test[df_test$year == 2012,]$Weekly_Sales * df_test[df_test$year == 2012,]$growth
df_test[df_test$year == 2013,]$Weekly_Sales <- df_test[df_test$year == 2013,]$Weekly_Sales * df_test[df_test$year == 2013,]$growth
write.csv(df_test[,c("Id","Weekly_Sales")], file="WMT_naivemean.csv", row.names=FALSE)
df_test$WS_naivemean <- df_test$Weekly_Sales
# Ensembles -- in this case, these don't work so well though. Better with more models
df_test$Weekly_Sales <- apply(df_test[,c("WS_FE_shift","WS_naivemean")],1,min)
#df_test$Weekly_Sales <- apply(df_test[,c("WS_FE_shift","WS_naivemean")],1,max)
#df_test$Weekly_Sales <- apply(df_test[,c("WS_FE_shift","WS_naivemean")],1,mean)
write.csv(df_test[,c("Id","Weekly_Sales")], file="WMT_ens.csv", row.names=FALSE)
wmaes_out <- c(4993.4, 5618.4, 3378.8, 3274.2)
names(wmaes_out) <- c("Linear", "Linear 2", "FE", "Shifted FE")
wmaes_out
Linear Linear 2 FE Shifted FE
4993.4 5618.4 3378.8 3274.2
LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gUHJvamVjdCINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KTm90ZSB0aGF0IHRoZSBkaXJlY3RvcmllcyB1c2VkIHRvIHN0b3JlIGRhdGEgYXJlIGxpa2VseSBkaWZmZXJlbnQgb24geW91ciBjb21wdXRlciwgYW5kIHN1Y2ggcmVmZXJlbmNlcyB3aWxsIG5lZWQgdG8gYmUgY2hhbmdlZCBiZWZvcmUgdXNpbmcgYW55IHN1Y2ggY29kZS4NCg0KYGBge3IgaGVscGVycywgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpodG1sX2RmIDwtIGZ1bmN0aW9uKHRleHQsIGNvbHM9TlVMTCwgY29sMT1GQUxTRSwgZnVsbD1GKSB7DQogIGlmKCFsZW5ndGgoY29scykpIHsNCiAgICBjb2xzPWNvbG5hbWVzKHRleHQpDQogIH0NCiAgaWYoIWNvbDEpIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpDQogIH0gZWxzZSB7DQogICAga2FibGUodGV4dCwiaHRtbCIsIGNvbC5uYW1lcyA9IGNvbHMsIGFsaWduID0gYygibCIscmVwKCdjJyxsZW5ndGgoY29scyktMSkpKSAlPiUNCiAgICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwiaG92ZXIiKSwgZnVsbF93aWR0aD1mdWxsKSAlPiUNCiAgICAgIGNvbHVtbl9zcGVjKDEsYm9sZD1UKQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCndlZWtseSA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9XTVRfdHJhaW4uY3N2IikNCndlZWtseS50ZXN0IDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1dNVF90ZXN0LmNzdiIpDQp3ZWVrbHkuZmVhdHVyZXMgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvV01UX2ZlYXR1cmVzLmNzdiIpDQp3ZWVrbHkuc3RvcmVzIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1dNVF9zdG9yZXMuY3N2IikNCmBgYA0KDQpgYGB7cn0NCndtYWUgPC0gZnVuY3Rpb24oYWN0dWFsLCBwcmVkaWN0ZWQsIGhvbGlkYXlzKSB7DQogIHN1bShhYnMoYWN0dWFsLXByZWRpY3RlZCkqKGhvbGlkYXlzKjQrMSkpIC8gKGxlbmd0aChhY3R1YWwpICsgNCpzdW0oaG9saWRheXMpKQ0KfQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICMgd2UnbGwgZXh0ZW5zaXZlbHkgdXNlIGRwbHlyIGhlcmUNCmxpYnJhcnkobHVicmlkYXRlKSAgIyBHcmVhdCBmb3Igc2ltcGxlIGRhdGUgZnVuY3Rpb25zDQpsaWJyYXJ5KGJyb29tKQ0Kd2Vla2x5IDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1dNVF90cmFpbi5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0Kd2Vla2x5LnRlc3QgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvV01UX3Rlc3QuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCndlZWtseS5mZWF0dXJlcyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9XTVRfZmVhdHVyZXMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCndlZWtseS5zdG9yZXMgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvV01UX3N0b3Jlcy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQpwcmVwcm9jZXNzX2RhdGEgPC0gZnVuY3Rpb24oZGYpIHsNCiAgIyBNZXJnZSB0aGUgZGF0YSB0b2dldGhlciAoUHVsbGVkIGZyb20gb3V0c2lkZSBvZiBmdW5jdGlvbiAtLSAic2NvcGluZyIpDQogIGRmIDwtIGlubmVyX2pvaW4oZGYsIHdlZWtseS5zdG9yZXMpDQogIGRmIDwtIGlubmVyX2pvaW4oZGYsIHdlZWtseS5mZWF0dXJlc1ssMToxMV0pDQogIA0KICAjIENvbXByZXNzIHRoZSB3ZWlyZCBtYXJrZG93biBpbmZvcm1hdGlvbiB0byAxIHZhcmlhYmxlDQogIGRmJG1hcmtkb3duIDwtIDANCiAgZGZbIWlzLm5hKGRmJE1hcmtEb3duMSksXSRtYXJrZG93biA8LSBkZlshaXMubmEoZGYkTWFya0Rvd24xKSxdJE1hcmtEb3duMQ0KICBkZlshaXMubmEoZGYkTWFya0Rvd24yKSxdJG1hcmtkb3duIDwtIGRmWyFpcy5uYShkZiRNYXJrRG93bjIpLF0kTWFya0Rvd24yDQogIGRmWyFpcy5uYShkZiRNYXJrRG93bjMpLF0kbWFya2Rvd24gPC0gZGZbIWlzLm5hKGRmJE1hcmtEb3duMyksXSRNYXJrRG93bjMNCiAgZGZbIWlzLm5hKGRmJE1hcmtEb3duNCksXSRtYXJrZG93biA8LSBkZlshaXMubmEoZGYkTWFya0Rvd240KSxdJE1hcmtEb3duNA0KICBkZlshaXMubmEoZGYkTWFya0Rvd241KSxdJG1hcmtkb3duIDwtIGRmWyFpcy5uYShkZiRNYXJrRG93bjUpLF0kTWFya0Rvd241DQogIA0KICAjIEZpeCBkYXRlcyBhbmQgYWRkIHVzZWZ1bCB0aW1lIHZhcmlhYmxlcw0KICBkZiRkYXRlIDwtIGFzLkRhdGUoZGYkRGF0ZSkNCiAgZGYkd2VlayA8LSB3ZWVrKGRmJGRhdGUpDQogIGRmJHllYXIgPC0geWVhcihkZiRkYXRlKQ0KICANCiAgZGYNCn0NCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GfQ0KZGYgPC0gcHJlcHJvY2Vzc19kYXRhKHdlZWtseSkNCmRmX3Rlc3QgPC0gcHJlcHJvY2Vzc19kYXRhKHdlZWtseS50ZXN0KQ0KYGBgDQoNCmBgYHtyfQ0KZGZbOTE6OTQsXSAlPiUNCiAgc2VsZWN0KFN0b3JlLCBkYXRlLCBtYXJrZG93biwgTWFya0Rvd24zLCBNYXJrRG93bjQsIE1hcmtEb3duNSkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0KZGZbMToyLF0gJT4lIHNlbGVjdChkYXRlLCB3ZWVrLCB5ZWFyKSAlPiUgaHRtbF9kZigpDQpgYGANCg0KYGBge3J9DQojIEZpbGwgaW4gbWlzc2luZyBDUEkgYW5kIFVuZW1wbG95bWVudCBkYXRhDQpkZl90ZXN0IDwtIGRmX3Rlc3QgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCB5ZWFyKSAlPiUNCiAgbXV0YXRlKENQST1pZmVsc2UoaXMubmEoQ1BJKSwgbWVhbihDUEksbmEucm09VCksIENQSSksDQogICAgICAgICBVbmVtcGxveW1lbnQ9aWZlbHNlKGlzLm5hKFVuZW1wbG95bWVudCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYW4oVW5lbXBsb3ltZW50LG5hLnJtPVQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVbmVtcGxveW1lbnQpKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Rn0NCiMgVW5pcXVlIElEcyBpbiB0aGUgZGF0YQ0KZGYkaWQgPC0gZGYkU3RvcmUgKjEwMDAwICsgZGYkd2VlayAqIDEwMCArIGRmJERlcHQNCmRmX3Rlc3QkaWQgPC0gZGZfdGVzdCRTdG9yZSAqMTAwMDAgKyBkZl90ZXN0JHdlZWsgKiAxMDAgKyBkZl90ZXN0JERlcHQNCg0KIyBVbmlxdWUgSUQgYW5kIGZhY3RvciBidWlsZGluZw0Kc3dkIDwtIGMoZGYkaWQsIGRmX3Rlc3QkaWQpICAjIFBvb2wgYWxsIElEcw0Kc3dkIDwtIHVuaXF1ZShzd2QpICAjIE9ubHkga2VlcCB1bmlxdWUgZWxlbWVudHMNCnN3ZCA8LSBkYXRhLmZyYW1lKGlkPXN3ZCkgICMgTWFrZSBhIGRhdGEgZnJhbWUNCnN3ZCRzd2QgPC0gZmFjdG9yKHN3ZCRpZCkgICMgRXh0cmFjdCBmYWN0b3JzIGZvciB1c2luZyBsYXRlcg0KDQojIEFkZCB1bmlxdWUgZmFjdG9ycyB0byBkYXRhIC0tIGVuc3VyZXMgc2FtZSBmYWN0b3JzIGZvciBib3RoIGRhdGEgc2V0cw0KZGYgPC0gbGVmdF9qb2luKGRmLHN3ZCkNCmRmX3Rlc3QgPC0gbGVmdF9qb2luKGRmX3Rlc3Qsc3dkKQ0KYGBgDQoNCmBgYHtyfQ0KZGZfdGVzdCRJZCA8LSBwYXN0ZTAoZGZfdGVzdCRTdG9yZSwnXycsZGZfdGVzdCREZXB0LCJfIixkZl90ZXN0JGRhdGUpDQpgYGANCg0KYGBge3J9DQpodG1sX2RmKGRmX3Rlc3RbYygyMDAwMCw0MDAwMCw2MDAwMCksYygiU3RvcmUiLCJ3ZWVrIiwiRGVwdCIsImlkIiwic3dkIiwiSWQiKV0pDQpgYGANCg0KYGBge3J9DQojIENhbGN1bGF0ZSBhdmVyYWdlIGJ5IHN0b3JlLWRlcHQgYW5kIGRpc3RyaWJ1dGUgdG8gZGZfdGVzdA0KZGYgPC0gZGYgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCBEZXB0KSAlPiUgDQogIG11dGF0ZShzdG9yZV9hdmc9bWVhbihXZWVrbHlfU2FsZXMsIHJtLm5hPVQpKSAlPiUNCiAgdW5ncm91cCgpDQpkZl9zYSA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoU3RvcmUsIERlcHQpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgc2VsZWN0KFN0b3JlLCBEZXB0LCBzdG9yZV9hdmcpICU+JQ0KICB1bmdyb3VwKCkNCmRmX3Rlc3QgPC0gbGVmdF9qb2luKGRmX3Rlc3QsIGRmX3NhKQ0KIyAzNiBvYnNlcnZhdGlvbnMgaGF2ZSBtZXNzZWQgdXAgZGVwYXJ0bWVudCBjb2RlcyAtLSBpZ25vcmUgKHNldCB0byAwKQ0KZGZfdGVzdFtpcy5uYShkZl90ZXN0JHN0b3JlX2F2ZyksXSRzdG9yZV9hdmcgIDwtIDANCg0KIyBDYWxjdWxhdGUgbXVsdGlwbGllcnMgYmFzZWQgb24gc3RvcmVfYXZnIChhbmQgcmVtb3ZpbmcgTmFOIGFuZCBJbmYpDQpkZiRXZWVrbHlfbXVsdCA8LSBkZiRXZWVrbHlfU2FsZXMgLyBkZiRzdG9yZV9hdmcNCmRmWyFpcy5maW5pdGUoZGYkV2Vla2x5X211bHQpLF0kV2Vla2x5X211bHQgPC0gTkENCmBgYA0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIG1lYW4gYnkgd2Vlay1zdG9yZS1kZXB0IGFuZCBkaXN0cmlidXRlIHRvIGRmX3Rlc3QNCmRmIDwtIGRmICU+JQ0KICBncm91cF9ieShTdG9yZSwgRGVwdCwgd2VlaykgJT4lDQogIG11dGF0ZShuYWl2ZV9tZWFuPW1lYW4oV2Vla2x5X1NhbGVzLCBybS5uYT1UKSkgJT4lDQogIHVuZ3JvdXAoKQ0KZGZfd20gPC0gZGYgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCBEZXB0LCB3ZWVrKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KFN0b3JlLCBEZXB0LCB3ZWVrLCBuYWl2ZV9tZWFuKQ0KZGZfdGVzdCA8LSBkZl90ZXN0ICU+JSBhcnJhbmdlKFN0b3JlLCBEZXB0LCB3ZWVrKQ0KZGZfdGVzdCA8LSBsZWZ0X2pvaW4oZGZfdGVzdCwgZGZfd20pDQpgYGANCg0KYGBge3J9DQp0YWJsZShpcy5uYShkZl90ZXN0JG5haXZlX21lYW4pKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCiMgdGhlcmUgYXJlIGEgbG90IG9mIHNjYXR0ZXJlZCBtaXNzaW5nIHdlZWtzIC0tIGZpbGwgd2l0aCBhIGxhZyBmaXJzdA0KZGZfdGVzdCRuYWl2ZV9uYSA8LSBpcy5uYShkZl90ZXN0JG5haXZlX21lYW4pDQpkZl90ZXN0IDwtIGRmX3Rlc3QgJT4lDQogIGFycmFuZ2UoU3RvcmUsIERlcHQsIGRhdGUpICU+JQ0KICBncm91cF9ieShTdG9yZSwgRGVwdCkgJT4lDQogIG11dGF0ZShuYWl2ZV9tZWFuPWlmZWxzZShpcy5uYShuYWl2ZV9tZWFuKSwgbGFnKG5haXZlX21lYW4pLG5haXZlX21lYW4pKSAlPiUNCiAgdW5ncm91cCgpDQpkZl90ZXN0IDwtIGRmX3Rlc3QgJT4lDQogIGFycmFuZ2UoU3RvcmUsIERlcHQsIGRhdGUpICU+JQ0KICBncm91cF9ieShTdG9yZSwgRGVwdCkgJT4lDQogIG11dGF0ZShuYWl2ZV9tZWFuPWlmZWxzZShpcy5uYShuYWl2ZV9tZWFuKSwgbGFnKG5haXZlX21lYW4sMiksbmFpdmVfbWVhbikpICU+JQ0KICB1bmdyb3VwKCkNCmRmX3Rlc3QgPC0gZGZfdGVzdCAlPiUNCiAgYXJyYW5nZShTdG9yZSwgRGVwdCwgZGF0ZSkgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCBEZXB0KSAlPiUNCiAgbXV0YXRlKG5haXZlX21lYW49aWZlbHNlKGlzLm5hKG5haXZlX21lYW4pLCBsZWFkKG5haXZlX21lYW4sMSksbmFpdmVfbWVhbikpICU+JQ0KICB1bmdyb3VwKCkNCmRmX3Rlc3QgPC0gZGZfdGVzdCAlPiUNCiAgYXJyYW5nZShTdG9yZSwgRGVwdCwgZGF0ZSkgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCBEZXB0KSAlPiUNCiAgbXV0YXRlKG5haXZlX21lYW49aWZlbHNlKGlzLm5hKG5haXZlX21lYW4pLCBsZWFkKG5haXZlX21lYW4sMiksbmFpdmVfbWVhbikpICU+JQ0KICB1bmdyb3VwKCkNCmRmX3Rlc3QkbmFpdmVfbWVhbiA8LSBpZmVsc2UoaXMubmEoZGZfdGVzdCRuYWl2ZV9tZWFuKSwgZGZfdGVzdCRzdG9yZV9hdmcsIGRmX3Rlc3QkbmFpdmVfbWVhbikNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD0zfQ0KZGYgJT4lIA0KICBncm91cF9ieSh3ZWVrLCBTdG9yZSkgJT4lDQogIG11dGF0ZShzYWxlcz1tZWFuKFdlZWtseV9TYWxlcykpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBnZ3Bsb3QoYWVzKHk9c2FsZXMsIHg9d2VlaywgY29sb3I9ZmFjdG9yKFN0b3JlKSkpICsNCiAgZ2VvbV9saW5lKCkgKyB4bGFiKCJXZWVrIikgKyB5bGFiKCJTYWxlcyBmb3IgU3RvcmUgKGRlcHQgYXZlcmFnZSkiKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCmBgYHtyfQ0KbW9kMSA8LSBsbShXZWVrbHlfbXVsdCB+IGZhY3RvcihJc0hvbGlkYXkpICsgZmFjdG9yKG1hcmtkb3duPjApICsNCiAgICAgICAgICAgICAgICAgICAgICAgICBtYXJrZG93biArIFRlbXBlcmF0dXJlICsNCiAgICAgICAgICAgICAgICAgICAgICAgICBGdWVsX1ByaWNlICsgQ1BJICsgVW5lbXBsb3ltZW50LA0KICAgICAgICAgICBkYXRhPWRmKQ0KdGlkeShtb2QxKQ0KZ2xhbmNlKG1vZDEpDQpgYGANCg0KYGBge3J9DQojIE91dCBvZiBzYW1wbGUgcmVzdWx0DQpkZl90ZXN0JFdlZWtseV9tdWx0IDwtIHByZWRpY3QobW9kMSwgZGZfdGVzdCkNCmRmX3Rlc3QkV2Vla2x5X1NhbGVzIDwtIGRmX3Rlc3QkV2Vla2x5X211bHQgKiBkZl90ZXN0JHN0b3JlX2F2Zw0KDQojIFJlcXVpcmVkIHRvIHN1Ym1pdCBhIGNzdiBvZiBJZCBhbmQgV2Vla2x5X1NhbGVzDQp3cml0ZS5jc3YoZGZfdGVzdFssYygiSWQiLCJXZWVrbHlfU2FsZXMiKV0sDQogICAgICAgICAgIldNVF9saW5lYXIuY3N2IiwNCiAgICAgICAgICByb3cubmFtZXM9RkFMU0UpDQoNCiMgdHJhY2sNCmRmX3Rlc3QkV1NfbGluZWFyIDwtIGRmX3Rlc3QkV2Vla2x5X1NhbGVzDQoNCiMgQ2hlY2sgaW4gc2FtcGxlIFdNQUUNCmRmJFdTX2xpbmVhciA8LSBwcmVkaWN0KG1vZDEsIGRmKSAqIGRmJHN0b3JlX2F2Zw0KdyA8LSB3bWFlKGFjdHVhbD1kZiRXZWVrbHlfU2FsZXMsIHByZWRpY3RlZD1kZiRXU19saW5lYXIsIGhvbGlkYXlzPWRmJElzSG9saWRheSkNCm5hbWVzKHcpIDwtICJMaW5lYXIiDQp3bWFlcyA8LSBjKHcpDQp3bWFlcw0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NH0NCndtYWVfb2JzIDwtIGZ1bmN0aW9uKGFjdHVhbCwgcHJlZGljdGVkLCBob2xpZGF5cykgew0KICBhYnMoYWN0dWFsLXByZWRpY3RlZCkqKGhvbGlkYXlzKjUrMSkgLyAobGVuZ3RoKGFjdHVhbCkgKyA0KnN1bShob2xpZGF5cykpDQp9DQpkZiR3bWFlcyAgPC0gd21hZV9vYnMoYWN0dWFsPWRmJFdlZWtseV9TYWxlcywgcHJlZGljdGVkPWRmJFdTX2xpbmVhciwNCiAgICAgICAgICAgICAgICAgICAgICBob2xpZGF5cz1kZiRJc0hvbGlkYXkpDQpnZ3Bsb3QoZGF0YT1kZiwgYWVzKHk9d21hZXMsIHg9d2VlaywgY29sb3I9ZmFjdG9yKElzSG9saWRheSkpKSArIA0KICBnZW9tX2ppdHRlcih3aWR0aD0wLjI1KSArIHhsYWIoIldlZWsiKSArIHlsYWIoIldNQUUiKQ0KYGBgDQoNCmBgYHtyfQ0KbW9kMiA8LSBsbShXZWVrbHlfbXVsdCB+IGZhY3Rvcih3ZWVrKSArIGZhY3RvcihJc0hvbGlkYXkpICsgZmFjdG9yKG1hcmtkb3duPjApICsNCiAgICAgICAgICAgICAgICAgICAgICAgICBtYXJrZG93biArIFRlbXBlcmF0dXJlICsNCiAgICAgICAgICAgICAgICAgICAgICAgICBGdWVsX1ByaWNlICsgQ1BJICsgVW5lbXBsb3ltZW50LA0KICAgICAgICAgICBkYXRhPWRmKQ0KdGlkeShtb2QyKQ0KZ2xhbmNlKG1vZDIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Rn0NCiMgT3V0IG9mIHNhbXBsZSByZXN1bHQNCmRmX3Rlc3QkV2Vla2x5X211bHQgPC0gcHJlZGljdChtb2QyLCBkZl90ZXN0KQ0KZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gZGZfdGVzdCRXZWVrbHlfbXVsdCAqIGRmX3Rlc3Qkc3RvcmVfYXZnDQoNCiMgUmVxdWlyZWQgdG8gc3VibWl0IGEgY3N2IG9mIElkIGFuZCBXZWVrbHlfU2FsZXMNCndyaXRlLmNzdihkZl90ZXN0WyxjKCJJZCIsIldlZWtseV9TYWxlcyIpXSwNCiAgICAgICAgICAiV01UX2xpbmVhcjIuY3N2IiwNCiAgICAgICAgICByb3cubmFtZXM9RkFMU0UpDQoNCiMgdHJhY2sNCmRmX3Rlc3QkV1NfbGluZWFyMiA8LSBkZl90ZXN0JFdlZWtseV9TYWxlcw0KDQojIENoZWNrIGluIHNhbXBsZSBXTUFFDQpkZiRXU19saW5lYXIyIDwtIHByZWRpY3QobW9kMiwgZGYpICogZGYkc3RvcmVfYXZnDQp3IDwtIHdtYWUoYWN0dWFsPWRmJFdlZWtseV9TYWxlcywgcHJlZGljdGVkPWRmJFdTX2xpbmVhcjIsIGhvbGlkYXlzPWRmJElzSG9saWRheSkNCm5hbWVzKHcpIDwtICJMaW5lYXIgMiINCndtYWVzIDwtIGMod21hZXMsIHcpDQp3bWFlcw0KYGBgDQoNCmBgYHtyfQ0Kd21hZXNfb3V0IDwtIGMoNDk5My40LCA1NjE4LjQpDQpuYW1lcyh3bWFlc19vdXQpIDwtIGMoIkxpbmVhciIsICJMaW5lYXIgMiIpDQpgYGANCg0KYGBge3J9DQp3bWFlc19vdXQNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00fQ0KZGYkd21hZXMgIDwtIHdtYWVfb2JzKGFjdHVhbD1kZiRXZWVrbHlfU2FsZXMsIHByZWRpY3RlZD1kZiRXU19saW5lYXIyLA0KICAgICAgICAgICAgICAgICAgICAgIGhvbGlkYXlzPWRmJElzSG9saWRheSkNCmdncGxvdChkYXRhPWRmLCBhZXMoeT13bWFlcywNCiAgICAgICAgICAgICAgICAgICAgeD13ZWVrLA0KICAgICAgICAgICAgICAgICAgICBjb2xvcj1mYWN0b3IoSXNIb2xpZGF5KSkpICsgDQogIGdlb21faml0dGVyKHdpZHRoPTAuMjUpICsgeGxhYigiV2VlayIpICsgeWxhYigiV01BRSIpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NH0NCmdncGxvdChkYXRhPWRmLCBhZXMoeT13bWFlX29icyhhY3R1YWw9ZGYkV2Vla2x5X1NhbGVzLCBwcmVkaWN0ZWQ9ZGYkV1NfbGluZWFyMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob2xpZGF5cz1kZiRJc0hvbGlkYXkpLA0KICAgICAgICAgICAgICAgICAgICB4PXdlZWssDQogICAgICAgICAgICAgICAgICAgIGNvbG9yPWZhY3RvcihTdG9yZSkpKSArIA0KICBnZW9tX2ppdHRlcih3aWR0aD0wLjI1KSArIHhsYWIoIldlZWsiKSArIHlsYWIoIldNQUUiKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTR9DQpnZ3Bsb3QoZGF0YT1kZiwgYWVzKHk9d21hZV9vYnMoYWN0dWFsPWRmJFdlZWtseV9TYWxlcywgcHJlZGljdGVkPWRmJFdTX2xpbmVhcjIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG9saWRheXM9ZGYkSXNIb2xpZGF5KSwNCiAgICAgICAgICAgICAgICAgICAgeD13ZWVrLA0KICAgICAgICAgICAgICAgICAgICBjb2xvcj1mYWN0b3IoRGVwdCkpKSArIA0KICBnZW9tX2ppdHRlcih3aWR0aD0wLjI1KSArIHhsYWIoIldlZWsiKSArIHlsYWIoIldNQUUiKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQpsaWJyYXJ5KGxmZSkNCm1vZDMgPC0gZmVsbShXZWVrbHlfbXVsdCB+IG1hcmtkb3duICsNCiAgICAgICAgICAgIFRlbXBlcmF0dXJlICsNCiAgICAgICAgICAgIEZ1ZWxfUHJpY2UgKw0KICAgICAgICAgICAgQ1BJICsNCiAgICAgICAgICAgIFVuZW1wbG95bWVudCB8IHN3ZCwgZGF0YT1kZikNCnRpZHkobW9kMykNCmdsYW5jZShtb2QzKQ0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdC5mZWxtIDwtIGZ1bmN0aW9uKG9iamVjdCwgbmV3ZGF0YSwgdXNlLmZlPVQsIC4uLikgew0KICAjIGNvbXBhdGlibGUgd2l0aCB0aWJibGVzDQogIG5ld2RhdGEgPC0gYXMuZGF0YS5mcmFtZShuZXdkYXRhKQ0KICBjbyA8LSBjb2VmKG9iamVjdCkNCiAgDQogIHkucHJlZCA8LSB0KGFzLm1hdHJpeCh1bm5hbWUoY28pKSkgJSolIHQoYXMubWF0cml4KG5ld2RhdGFbLG5hbWVzKGNvKV0pKQ0KICANCiAgZmUudmFycyA8LSBuYW1lcyhvYmplY3QkZmUpDQogIA0KICBhbGwuZmUgPC0gZ2V0ZmUob2JqZWN0KQ0KICBmb3IgKGZlLnZhciBpbiBmZS52YXJzKSB7DQogICAgbGV2ZWwgPC0gYWxsLmZlW2FsbC5mZSRmZSA9PSBmZS52YXIsXQ0KICAgIGZyb3dzIDwtIG1hdGNoKG5ld2RhdGFbW2ZlLnZhcl1dLGxldmVsJGlkeCkNCiAgICBteWZlIDwtIGxldmVsJGVmZmVjdFtmcm93c10NCiAgICBteWZlW2lzLm5hKG15ZmUpXSA9IDANCiAgICAgIA0KICAgIHkucHJlZCA8LSB5LnByZWQgKyBteWZlDQogIH0NCiAgYXMudmVjdG9yKHkucHJlZCkNCn0NCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GfQ0KIyBPdXQgb2Ygc2FtcGxlIHJlc3VsdA0KZGZfdGVzdCRXZWVrbHlfbXVsdCA8LSBwcmVkaWN0KG1vZDMsIGRmX3Rlc3QpDQpkZl90ZXN0JFdlZWtseV9TYWxlcyA8LSBkZl90ZXN0JFdlZWtseV9tdWx0ICogZGZfdGVzdCRzdG9yZV9hdmcNCg0KIyBSZXF1aXJlZCB0byBzdWJtaXQgYSBjc3Ygb2YgSWQgYW5kIFdlZWtseV9TYWxlcw0Kd3JpdGUuY3N2KGRmX3Rlc3RbLGMoIklkIiwiV2Vla2x5X1NhbGVzIildLA0KICAgICAgICAgICJXTVRfRkUuY3N2IiwNCiAgICAgICAgICByb3cubmFtZXM9RkFMU0UpDQoNCiMgdHJhY2sNCmRmX3Rlc3QkV1NfRkUgPC0gZGZfdGVzdCRXZWVrbHlfU2FsZXMNCg0KIyBDaGVjayBpbiBzYW1wbGUgV01BRQ0KZGYkV1NfRkUgPC0gcHJlZGljdChtb2QzLCBkZikgKiBkZiRzdG9yZV9hdmcNCncgPC0gd21hZShhY3R1YWw9ZGYkV2Vla2x5X1NhbGVzLCBwcmVkaWN0ZWQ9ZGYkV1NfRkUsIGhvbGlkYXlzPWRmJElzSG9saWRheSkNCm5hbWVzKHcpIDwtICJGRSINCndtYWVzIDwtIGMod21hZXMsIHcpDQp3bWFlcw0KYGBgDQoNCmBgYHtyfQ0Kd21hZXNfb3V0IDwtIGMoNDk5My40LCA1NjE4LjQsIDMzNzguOCkNCm5hbWVzKHdtYWVzX291dCkgPC0gYygiTGluZWFyIiwgIkxpbmVhciAyIiwgIkZFIikNCmBgYA0KDQpgYGB7cn0NCndtYWVzX291dA0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTR9DQpkZiR3bWFlcyAgPC0gd21hZV9vYnMoYWN0dWFsPWRmJFdlZWtseV9TYWxlcywgcHJlZGljdGVkPWRmJFdTX0ZFLA0KICAgICAgICAgICAgICAgICAgICAgIGhvbGlkYXlzPWRmJElzSG9saWRheSkNCmdncGxvdChkYXRhPWRmLCBhZXMoeT13bWFlcywNCiAgICAgICAgICAgICAgICAgICAgeD13ZWVrLA0KICAgICAgICAgICAgICAgICAgICBjb2xvcj1mYWN0b3IoSXNIb2xpZGF5KSkpICsgDQogIGdlb21faml0dGVyKHdpZHRoPTAuMjUpICsgeGxhYigiV2VlayIpICsgeWxhYigiV01BRSIpDQpgYGANCg0KYGBge3J9DQojIEZ1bmN0aW9uIHRvIG1hcCBob2xpZGF5cw0KaG9saWRmIDwtIGZ1bmN0aW9uKGRmLCB5ZWFycywgd2Vla3MpIHsNCiAgaWYobGVuZ3RoKHllYXJzKSA9PSAyKSB7DQogICAgeWVhcnMgPSBjKHllYXJzLCA5OTk5KQ0KICAgIHdlZWtzID0gYyh3ZWVrcywgOTkpDQogIH0NCiAgZGYgJT4lDQogIGZpbHRlcih3ZWVrKGRmJGRhdGUpID09IHdlZWtzWzFdICYgeWVhcihkZiRkYXRlKSA9PSB5ZWFyc1sxXSB8DQogICAgICAgICB3ZWVrKGRmJGRhdGUpID09IHdlZWtzWzJdICYgeWVhcihkZiRkYXRlKSA9PSB5ZWFyc1syXSB8DQogICAgICAgICB3ZWVrKGRmJGRhdGUpID09IHdlZWtzWzNdICYgeWVhcihkZiRkYXRlKSA9PSB5ZWFyc1szXSkgJT4lDQogIHNlbGVjdChTdG9yZSxEZXB0LFdlZWtseV9TYWxlcykgJT4lDQogIGdyb3VwX2J5KFN0b3JlLERlcHQpICU+JQ0KICBtdXRhdGUoaG9saWRheV9zYWxlcz1tZWFuKFdlZWtseV9TYWxlcyxybS5uYT1UKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChTdG9yZSxEZXB0LGhvbGlkYXlfc2FsZXMpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkNCn0NCiMgQWRkIGhvbGlkYXkgc2hpZnQgdG8gZGF0YQ0KYWRkX2hvbGlkYXkgPC0gZnVuY3Rpb24oZGYsIGhvbGlkYXksIHdrKSB7DQogIHEgPC0gbGVmdF9qb2luKGRmLCBob2xpZGF5KQ0KICBxIDwtIHEgJT4lIHRyYW5zbXV0ZShXZWVrbHlfU2FsZXM9aWZlbHNlKHdlZWs9PXdrLGhvbGlkYXlfc2FsZXMsV2Vla2x5X1NhbGVzKSkNCiAgcVtbMV1dDQp9DQoNCiMgU3RhcnQgd2l0aCBvdXIgRkUgYXBwcm9hY2gNCmRmX3Rlc3QkV2Vla2x5X1NhbGVzIDwtIGRmX3Rlc3QkV1NfRkUNCg0KIyBDSFJJU1RNQVMNCiMgMjAxMDogNTMsIDIwMTE6IDUyLCAyMDEyOiAqNTIqDQojIEdldCBhIGRmIHdpdGggQ2hyaXN0bWFzIHNwZWNpZmljIHNhbGVzDQpzX3htYXMgPC0gaG9saWRmKGRmLCBjKDIwMTAsMjAxMSksIGMoNTMsNTIpKQ0Kc194bWFzX20xIDwtIGhvbGlkZihkZiwgYygyMDEwLDIwMTEpLCBjKDUyLDUxKSkNCg0KZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gYWRkX2hvbGlkYXkoZGZfdGVzdCwgc194bWFzLCA1MikNCmRmX3Rlc3QkV2Vla2x5X1NhbGVzIDwtIGFkZF9ob2xpZGF5KGRmX3Rlc3QsIHNfeG1hc19tMSwgNTEpDQoNCiMgQkxBQ0sgRlJJREFZIDIwMTAtMTEtMjY6IDQ4OyAyMDExLTExLTI1OiA0NywgMjAxMi0xMS0yMzogKjQ3Kg0Kc19iZiA8LSBob2xpZGYoZGYsIGMoMjAxMCwyMDExKSwgYyg0OCw0NykpDQoNCmRmX3Rlc3QkV2Vla2x5X1NhbGVzIDwtIGFkZF9ob2xpZGF5KGRmX3Rlc3QsIHNfYmYsIDQ3KQ0KDQojIE5vdGUgdGhhdCB0aGUgc3VwZXJib3dsIGlzIHdlZWtzIDYsIDYsIDcsICo2Kg0Kc19zYiA8LSBob2xpZGYoZGYsIGMoMjAxMCwyMDExLDIwMTIpLCBjKDYsNiw3KSkNCnNfc2JfbTEgPC0gaG9saWRmKGRmLCBjKDIwMTAsMjAxMSwyMDEyKSwgYyg1LDUsNikpDQoNCmRmX3Rlc3QkV2Vla2x5X1NhbGVzIDwtIGFkZF9ob2xpZGF5KGRmX3Rlc3QsIHNfc2IsIDcpDQpkZl90ZXN0JFdlZWtseV9TYWxlcyA8LSBhZGRfaG9saWRheShkZl90ZXN0LCBzX3NiX20xLCA2KQ0KDQojIENsZWFuIHVwIGFueSBtaXNzaW5nIHZhbHVlcyBhZGRlZA0KZGZfdGVzdFtpcy5uYShkZl90ZXN0JFdlZWtseV9TYWxlcyksXSRXZWVrbHlfU2FsZXMgPC0gZGZfdGVzdFtpcy5uYShkZl90ZXN0JFdlZWtseV9TYWxlcyksXSRuYWl2ZV9tZWFuDQoNCg0KIyBDYWxjdWxhdGUgeWVhcmx5IGdyb3d0DQojIEEgYml0IGRpZmZpY3VsdCBzaW5jZSB0aGUgZGF0YSBpcyBhIHBhcnRpYWwgeWVhciwgYSBmdWxsIHllYXIsIGFuZCBhIHBhcnRpYWwgeWVhcg0KeWcxIDwtIGRmICU+JQ0KICBhcnJhbmdlKFN0b3JlLCB5ZWFyKSAlPiUNCiAgZmlsdGVyKHllYXIgPT0gMjAxMCkgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCB5ZWFyKSAlPiUNCiAgbXV0YXRlKHNhbGVzMjAxMD1tZWFuKFdlZWtseV9TYWxlcykpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoU3RvcmUsIHNhbGVzMjAxMCkNCnlnMiA8LSBkZiAlPiUNCiAgYXJyYW5nZShTdG9yZSwgeWVhcikgJT4lDQogIGZpbHRlcihkYXRlID4gYXMuRGF0ZSgiMjAxMS0wMi0wNSIpICYgeWVhciA9PSAyMDExKSAlPiUNCiAgZ3JvdXBfYnkoU3RvcmUsIHllYXIpICU+JQ0KICBtdXRhdGUoc2FsZXMyMDExYT1tZWFuKFdlZWtseV9TYWxlcykpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoU3RvcmUsIHNhbGVzMjAxMWEpDQp5ZzMgPC0gZGYgJT4lDQogIGFycmFuZ2UoU3RvcmUsIHllYXIpICU+JQ0KICBmaWx0ZXIoZGF0ZSA8IGFzLkRhdGUoIjIwMTEtMTAtMDYiKSAmIHllYXIgPT0gMjAxMSkgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCB5ZWFyKSAlPiUNCiAgbXV0YXRlKHNhbGVzMjAxMWI9bWVhbihXZWVrbHlfU2FsZXMpKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KFN0b3JlLCBzYWxlczIwMTFiKQ0KeWc0IDwtIGRmICU+JQ0KICBhcnJhbmdlKFN0b3JlLCB5ZWFyKSAlPiUNCiAgZmlsdGVyKHllYXIgPT0gMjAxMikgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCB5ZWFyKSAlPiUNCiAgbXV0YXRlKHNhbGVzMjAxMj1tZWFuKFdlZWtseV9TYWxlcykpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoU3RvcmUsIHNhbGVzMjAxMikNCnlnIDwtIGZ1bGxfam9pbih5ZzEseWcyKSAlPiUgZnVsbF9qb2luKHlnMykgJT4lIGZ1bGxfam9pbih5ZzQpDQp5ZyA8LSB5ZyAlPiUNCiAgbXV0YXRlKGdyb3d0aDE9c2FsZXMyMDExYS9zYWxlczIwMTAsDQogICAgICAgICBncm93dGgyPXNhbGVzMjAxMi9zYWxlczIwMTFiLA0KICAgICAgICAgZ3Jvd3RoPWlmZWxzZShpcy5uYShncm93dGgxKSxpZmVsc2UoaXMubmEoZ3Jvd3RoMiksMSxncm93dGgyKSxpZmVsc2UoaXMubmEoZ3Jvd3RoMiksMSwoZ3Jvd3RoMStncm93dGgyKS8yKSkpICU+JQ0KICBzZWxlY3QoU3RvcmUsIGdyb3d0aCkNCg0KIyBBZGQgZ3Jvd3RoIGRhdGEgdG8gb3VyIGRmX3Rlc3QgZGF0YSBmcmFtZQ0KZGZfdGVzdCA8LSBsZWZ0X2pvaW4oZGZfdGVzdCwgeWcpDQpkZl90ZXN0W2RmX3Rlc3QkeWVhciA9PSAyMDEyLF0kV2Vla2x5X1NhbGVzIDwtIGRmX3Rlc3RbZGZfdGVzdCR5ZWFyID09IDIwMTIsXSRXZWVrbHlfU2FsZXMgKiBkZl90ZXN0W2RmX3Rlc3QkeWVhciA9PSAyMDEyLF0kZ3Jvd3RoDQpkZl90ZXN0W2RmX3Rlc3QkeWVhciA9PSAyMDEzLF0kV2Vla2x5X1NhbGVzIDwtIGRmX3Rlc3RbZGZfdGVzdCR5ZWFyID09IDIwMTMsXSRXZWVrbHlfU2FsZXMgKiBkZl90ZXN0W2RmX3Rlc3QkeWVhciA9PSAyMDEzLF0kZ3Jvd3RoDQoNCndyaXRlLmNzdihkZl90ZXN0WyxjKCJJZCIsIldlZWtseV9TYWxlcyIpXSwgZmlsZT0iV01UX0ZFX3NoaWZ0LmNzdiIsIHJvdy5uYW1lcz1GQUxTRSkNCmRmX3Rlc3QkV1NfRkVfc2hpZnQgPC0gZGZfdGVzdCRXZWVrbHlfU2FsZXMNCg0KIyBCT05VUzogRW5zZW1ibGluZw0KIyBFbnNlbWJsZTogQnVpbGRpbmcgbXVsdGlwbGUgbW9kZWxzIGFuZCBjb21iaW5pbmcgdGhlbSBpbnRvIDENCg0KIyBUaGlzIGV4YW1wbGU6IGFkZCBpbiBhIG5haXZlIG1lYW4gYXBwcm9hY2ggd2l0aCBzaGlmdGluZyArIGdyb3d0aA0KZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gZGZfdGVzdCRuYWl2ZV9tZWFuDQpkZl90ZXN0JFdlZWtseV9TYWxlcyA8LSBhZGRfaG9saWRheShkZl90ZXN0LCBzX3htYXMsIDUyKQ0KZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gYWRkX2hvbGlkYXkoZGZfdGVzdCwgc194bWFzX20xLCA1MSkNCmRmX3Rlc3QkV2Vla2x5X1NhbGVzIDwtIGFkZF9ob2xpZGF5KGRmX3Rlc3QsIHNfYmYsIDQ3KQ0KZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gYWRkX2hvbGlkYXkoZGZfdGVzdCwgc19zYiwgNykNCmRmX3Rlc3QkV2Vla2x5X1NhbGVzIDwtIGFkZF9ob2xpZGF5KGRmX3Rlc3QsIHNfc2JfbTEsIDYpDQpkZl90ZXN0W2lzLm5hKGRmX3Rlc3QkV2Vla2x5X1NhbGVzKSxdJFdlZWtseV9TYWxlcyA8LSBkZl90ZXN0W2lzLm5hKGRmX3Rlc3QkV2Vla2x5X1NhbGVzKSxdJG5haXZlX21lYW4NCmRmX3Rlc3RbZGZfdGVzdCR5ZWFyID09IDIwMTIsXSRXZWVrbHlfU2FsZXMgPC0gZGZfdGVzdFtkZl90ZXN0JHllYXIgPT0gMjAxMixdJFdlZWtseV9TYWxlcyAqIGRmX3Rlc3RbZGZfdGVzdCR5ZWFyID09IDIwMTIsXSRncm93dGgNCmRmX3Rlc3RbZGZfdGVzdCR5ZWFyID09IDIwMTMsXSRXZWVrbHlfU2FsZXMgPC0gZGZfdGVzdFtkZl90ZXN0JHllYXIgPT0gMjAxMyxdJFdlZWtseV9TYWxlcyAqIGRmX3Rlc3RbZGZfdGVzdCR5ZWFyID09IDIwMTMsXSRncm93dGgNCndyaXRlLmNzdihkZl90ZXN0WyxjKCJJZCIsIldlZWtseV9TYWxlcyIpXSwgZmlsZT0iV01UX25haXZlbWVhbi5jc3YiLCByb3cubmFtZXM9RkFMU0UpDQpkZl90ZXN0JFdTX25haXZlbWVhbiA8LSBkZl90ZXN0JFdlZWtseV9TYWxlcw0KDQojIEVuc2VtYmxlcyAtLSBpbiB0aGlzIGNhc2UsIHRoZXNlIGRvbid0IHdvcmsgc28gd2VsbCB0aG91Z2guICBCZXR0ZXIgd2l0aCBtb3JlIG1vZGVscw0KZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gYXBwbHkoZGZfdGVzdFssYygiV1NfRkVfc2hpZnQiLCJXU19uYWl2ZW1lYW4iKV0sMSxtaW4pDQojZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gYXBwbHkoZGZfdGVzdFssYygiV1NfRkVfc2hpZnQiLCJXU19uYWl2ZW1lYW4iKV0sMSxtYXgpDQojZGZfdGVzdCRXZWVrbHlfU2FsZXMgPC0gYXBwbHkoZGZfdGVzdFssYygiV1NfRkVfc2hpZnQiLCJXU19uYWl2ZW1lYW4iKV0sMSxtZWFuKQ0KDQp3cml0ZS5jc3YoZGZfdGVzdFssYygiSWQiLCJXZWVrbHlfU2FsZXMiKV0sIGZpbGU9IldNVF9lbnMuY3N2Iiwgcm93Lm5hbWVzPUZBTFNFKQ0KDQpgYGANCg0KYGBge3J9DQp3bWFlc19vdXQgPC0gYyg0OTkzLjQsIDU2MTguNCwgMzM3OC44LCAzMjc0LjIpDQpuYW1lcyh3bWFlc19vdXQpIDwtIGMoIkxpbmVhciIsICJMaW5lYXIgMiIsICJGRSIsICJTaGlmdGVkIEZFIikNCmBgYA0KDQpgYGB7cn0NCndtYWVzX291dA0KYGBgDQoNCg==