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)
df <- read.csv("../../Data/Session_3-1.csv", stringsAsFactors=FALSE)
wmt <- filter(df, tic == "WMT")
# load in relevant data from Session 2
load("../../Data/Session_2_export.RData")
expectations <- read_csv("../../Data/general-business-expectations-by-detailed-services-industry-quarterly.csv") %>%
mutate(year = as.numeric(substr(quarter, 1, 4))) %>% # split out year
mutate(quarter = as.numeric(substr(quarter, 7, 7))) %>% # split out quarter
mutate(value = as.numeric(value)) # Ensue value is numeric
Parsed with column specification:
cols(
quarter = [31mcol_character()[39m,
level_1 = [31mcol_character()[39m,
level_2 = [31mcol_character()[39m,
level_3 = [31mcol_character()[39m,
value = [31mcol_character()[39m
)
NAs introduced by coercion
# extract out Q1, finance only
expectations_avg <- expectations %>%
filter(quarter == 1, # Keep only the first quarter
level_2 == "Financial & Insurance") %>% # Keep only financial responses
group_by(year) %>% # Group data by year
mutate(fin_sentiment=mean(value, na.rm=TRUE)) %>% # Calculate average
slice(1) # Take only 1 row per group
library(DT)
expectations %>%
arrange(level_2, level_3, desc(year)) %>% # sort the data
select(year, quarter, level_2, level_3, value) %>% # keep only these variables
datatable(options = list(pageLength = 5), rownames=FALSE) # display using DT
# subset out our Singaporean data, since our macro data is Singapore-specific
df_SG <- df_clean %>% filter(fic == "SGP")
# Create year in df_SG (date is given by datadate as YYYYMMDD)
df_SG$year = round(df_SG$datadate / 10000, digits=0)
# Combine datasets
# Notice how it automatically figures out to join by "year"
df_SG_macro <- left_join(df_SG, expectations_avg[,c("year","fin_sentiment")])
Joining, by = "year"
macro1 <- lm(revt_lead ~ revt + act + che + lct + dp + ebit + fin_sentiment,
data=df_SG_macro)
library(broom)
tidy(macro1)
df_SG_macro %>%
ggplot(aes(y=revt_lead,
x=fin_sentiment)) +
geom_point()

df_SG_macro %>%
ggplot(aes(y=revt_lead,
x=scale(fin_sentiment) * revt)) +
geom_point()

# Scale creates z-scores, but returns a matrix by default. [,1] gives a vector
df_SG_macro$fin_sent_scaled <- scale(df_SG_macro$fin_sentiment)[,1]
macro3 <-
lm(revt_lead ~ revt + act + che + lct + dp + ebit + fin_sent_scaled:revt,
data=df_SG_macro)
tidy(macro3)
glance(macro3)
baseline <-
lm(revt_lead ~ revt + act + che + lct + dp + ebit,
data=df_SG_macro[!is.na(df_SG_macro$fin_sentiment),])
glance(baseline)
glance(macro3)
anova(baseline, macro3, test="Chisq")
Analysis of Variance Table
Model 1: revt_lead ~ revt + act + che + lct + dp + ebit
Model 2: revt_lead ~ revt + act + che + lct + dp + ebit + fin_sent_scaled:revt
Res.Df RSS Df Sum of Sq Pr(>Chi)
1 304 14285622
2 303 13949301 1 336321 0.006875 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
r_sd <- round(sd(df_SG_macro$fin_sentiment, na.rm=T),1)
r_min <- min(df_SG_macro$fin_sentiment, na.rm=T)
r_max <- max(df_SG_macro$fin_sentiment, na.rm=T)
rev <- macro3$coefficients[["revt:fin_sent_scaled"]]
r_rev = round(100 * rev,0)
rev_min <- round((r_min / r_sd * rev + rev)*100,1)
rev_max <- round((r_max / r_sd * rev + rev)*100,1)
p_uol <- predict(forecast2, uol[uol$fyear==2017,])
p_base <- predict(baseline,
df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear==2017,])
p_macro <- predict(macro3,
df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear==2017,])
p_world <- predict(forecast4,
df_clean[df_clean$isin=="SG1S83002349" & df_clean$fyear==2017,])
preds <- c(p_uol, p_base, p_macro, p_world)
names(preds) <- c("UOL 2018 UOL", "UOL 2018 Base", "UOL 2018 Macro",
"UOL 2018 World")
preds
UOL 2018 UOL UOL 2018 Base UOL 2018 Macro UOL 2018 World
3177.073 2086.437 2024.842 2589.636
library(plotly)
df_SG_macro$pred_base <- predict(baseline, df_SG_macro)
df_SG_macro$pred_macro <- predict(macro3, df_SG_macro)
df_clean$pred_world <- predict(forecast4, df_clean)
uol$pred_uol <- predict(forecast2, uol)
df_preds <- data.frame(preds=preds, fyear=c(2018,2018,2018,2018), model=c("UOL only", "Base", "Macro", "World"))
plot <- ggplot() +
geom_point(data=df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,], aes(y=revt_lead,x=fyear, color="Actual")) +
geom_line(data=df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,], aes(y=revt_lead,x=fyear, color="Actual")) +
geom_point(data=uol[uol$fyear < 2017,], aes(y=pred_uol,x=fyear, color="UOL only")) +
geom_line(data=uol[uol$fyear < 2017,], aes(y=pred_uol,x=fyear, color="UOL only")) +
geom_point(data=df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,], aes(y=pred_base,x=fyear, color="Base")) +
geom_line(data=df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,], aes(y=pred_base,x=fyear, color="Base")) +
geom_point(data=df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,], aes(y=pred_macro,x=fyear, color="Macro")) +
geom_line(data=df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,], aes(y=pred_macro,x=fyear, color="Macro")) +
geom_point(data=df_clean[df_clean$isin=="SG1S83002349" & df_clean$fyear < 2017,], aes(y=pred_world,x=fyear, color="World")) +
geom_line(data=df_clean[df_clean$isin=="SG1S83002349" & df_clean$fyear < 2017,], aes(y=pred_world,x=fyear, color="World")) +
geom_point(data=df_preds, aes(y=preds, x=fyear, color=model), size=1.5, shape=18)
ggplotly(plot)
actual_series <- df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,]$revt_lead
uol_series <- uol[uol$fyear < 2017,]$pred_uol
base_series <- df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,]$pred_base
macro_series <- df_SG_macro[df_SG_macro$isin=="SG1S83002349" & df_SG_macro$fyear < 2017,]$pred_macro
world_series <- df_clean[df_clean$isin=="SG1S83002349" & df_clean$fyear < 2017,]$pred_world
# series vectors calculated here -- See appendix
rmse <- function(v1, v2) {
sqrt(mean((v1 - v2)^2, na.rm=T))
}
rmse <- c(rmse(actual_series, uol_series), rmse(actual_series, base_series),
rmse(actual_series, macro_series), rmse(actual_series, world_series))
names(rmse) <- c("UOL 2018 UOL", "UOL 2018 Base", "UOL 2018 Macro", "UOL 2018 World")
rmse
UOL 2018 UOL UOL 2018 Base UOL 2018 Macro UOL 2018 World
175.5609 301.3161 344.9681 332.8101
preds
UOL 2018 UOL UOL 2018 Base UOL 2018 Macro UOL 2018 World
3177.073 2086.437 2024.842 2589.636
library(tidyverse) # As always
library(plotly) # interactive graphs
library(lubridate) # import some sensible date functions
# Generate quarter over quarter growth "revtq_gr"
df <- df %>% group_by(gvkey) %>% mutate(revtq_gr=revtq / lag(revtq) - 1) %>% ungroup()
# Generate year-over-year growth "revtq_yoy"
df <- df %>% group_by(gvkey) %>% mutate(revtq_yoy=revtq / lag(revtq, 4) - 1) %>% ungroup()
# Generate first difference "revtq_d"
df <- df %>% group_by(gvkey) %>% mutate(revtq_d=revtq - lag(revtq)) %>% ungroup()
# Generate a proper date
# Date was YYMMDDs10: YYYY/MM/DD, can be converted from text to date easily
df$date <- as.Date(df$datadate) # Built in to R
html_df(head(df[,c("conm","date","revtq","revtq_gr", "revtq_yoy", "revtq_d")]))
conm |
date |
revtq |
revtq_gr |
revtq_yoy |
revtq_d |
ALLIED STORES |
1962-04-30 |
156.5 |
NA |
NA |
NA |
ALLIED STORES |
1962-07-31 |
161.9 |
0.0345048 |
NA |
5.4 |
ALLIED STORES |
1962-10-31 |
176.9 |
0.0926498 |
NA |
15.0 |
ALLIED STORES |
1963-01-31 |
275.5 |
0.5573770 |
NA |
98.6 |
ALLIED STORES |
1963-04-30 |
171.1 |
-0.3789474 |
0.0932907 |
-104.4 |
ALLIED STORES |
1963-07-31 |
182.2 |
0.0648743 |
0.1253860 |
11.1 |
head(df[,c("conm","date", "datadate")])
# Custom Function to generate a series of lags
library(rlang)
multi_lag <- function(df, lags, var, postfix="") {
var <- enquo(var)
quosures <- map(lags, ~quo(lag(!!var, !!.x))) %>%
set_names(paste0(quo_text(var), postfix, lags))
return(mutate(group_by(df, gvkey), !!!quosures))
}
# Generate lags "revtq_l#"
df <- multi_lag(df, 1:8, revtq, "_l")
# Generate changes "revtq_gr#"
df <- multi_lag(df, 1:8, revtq_gr)
# Generate year-over-year changes "revtq_yoy#"
df <- multi_lag(df, 1:8, revtq_yoy)
# Generate first differences "revtq_d#"
df <- multi_lag(df, 1:8, revtq_d)
# Equivalent brute force code for this is in the appendix
html_df(head(df[,c("conm","date","revtq","revtq_l1", "revtq_l2", "revtq_l3", "revtq_l4")]))
conm |
date |
revtq |
revtq_l1 |
revtq_l2 |
revtq_l3 |
revtq_l4 |
ALLIED STORES |
1962-04-30 |
156.5 |
NA |
NA |
NA |
NA |
ALLIED STORES |
1962-07-31 |
161.9 |
156.5 |
NA |
NA |
NA |
ALLIED STORES |
1962-10-31 |
176.9 |
161.9 |
156.5 |
NA |
NA |
ALLIED STORES |
1963-01-31 |
275.5 |
176.9 |
161.9 |
156.5 |
NA |
ALLIED STORES |
1963-04-30 |
171.1 |
275.5 |
176.9 |
161.9 |
156.5 |
ALLIED STORES |
1963-07-31 |
182.2 |
171.1 |
275.5 |
176.9 |
161.9 |
# Clean the data: Replace NaN, Inf, and -Inf with NA
df <- df %>%
mutate_if(is.numeric, list(~replace(., !is.finite(.), NA)))
`mutate_if()` ignored the following grouping variables:
Column `gvkey`
# Split into training and testing data
# Training data: We'll use data released before 2015
train <- filter(df, year(date) < 2015)
# Testing data: We'll use data released 2015 through 2018
test <- filter(df, year(date) >= 2015)
summary(df[,c("revtq","revtq_gr","revtq_yoy", "revtq_d","fqtr")])
revtq revtq_gr revtq_yoy revtq_d fqtr
Min. : 0.00 Min. :-1.0000 Min. :-1.0000 Min. :-24325.21 Min. :1.000
1st Qu.: 64.46 1st Qu.:-0.1112 1st Qu.: 0.0077 1st Qu.: -19.33 1st Qu.:1.000
Median : 273.95 Median : 0.0505 Median : 0.0740 Median : 4.30 Median :2.000
Mean : 2439.38 Mean : 0.0650 Mean : 0.1273 Mean : 22.66 Mean :2.478
3rd Qu.: 1254.21 3rd Qu.: 0.2054 3rd Qu.: 0.1534 3rd Qu.: 55.02 3rd Qu.:3.000
Max. :136267.00 Max. :14.3333 Max. :47.6600 Max. : 15495.00 Max. :4.000
NA's :367 NA's :690 NA's :940 NA's :663
# These functions are a bit ugly, but can construct many charts quickly
# eval(parse(text=var)) is just a way to convert the string name to a variable reference
# Density plot for 1st to 99th percentile of data
plt_dist <- function(df,var) {
df %>%
filter(eval(parse(text=var)) < quantile(eval(parse(text=var)),0.99, na.rm=TRUE),
eval(parse(text=var)) > quantile(eval(parse(text=var)),0.01, na.rm=TRUE)) %>%
ggplot(aes(x=eval(parse(text=var)))) +
geom_density() + xlab(var)
}
# Density plot for 1st to 99th percentile of both columns
plt_bar <- function(df,var) {
df %>%
filter(eval(parse(text=var)) < quantile(eval(parse(text=var)),0.99, na.rm=TRUE),
eval(parse(text=var)) > quantile(eval(parse(text=var)),0.01, na.rm=TRUE)) %>%
ggplot(aes(y=eval(parse(text=var)), x=fqtr)) +
geom_bar(stat = "summary", fun.y = "mean") + xlab(var)
}
# Scatter plot with lag for 1st to 99th percentile of data
plt_sct <- function(df,var1, var2) {
df %>%
filter(eval(parse(text=var1)) < quantile(eval(parse(text=var1)),0.99, na.rm=TRUE),
eval(parse(text=var2)) < quantile(eval(parse(text=var2)),0.99, na.rm=TRUE),
eval(parse(text=var1)) > quantile(eval(parse(text=var1)),0.01, na.rm=TRUE),
eval(parse(text=var2)) > quantile(eval(parse(text=var2)),0.01, na.rm=TRUE)) %>%
ggplot(aes(y=eval(parse(text=var1)), x=eval(parse(text=var2)), color=factor(fqtr))) +
geom_point() + geom_smooth(method = "lm") + ylab(var1) + xlab(var2)
}
plt_dist(train, "revtq")

plt_dist(train, "revtq_gr")

plt_dist(train, "revtq_yoy")

plt_dist(train, "revtq_d")

plt_bar(train, "revtq")

plt_bar(train, "revtq_gr")

plt_bar(train, "revtq_yoy")

plt_bar(train, "revtq_d")

plt_sct(train, "revtq", "revtq_l1")

plt_sct(train, "revtq_gr", "revtq_gr1")

plt_sct(train, "revtq_yoy", "revtq_yoy1")

plt_sct(train, "revtq_d", "revtq_d1")

cor(train[,c("revtq","revtq_l1","revtq_l2","revtq_l3", "revtq_l4")],
use="complete.obs")
revtq revtq_l1 revtq_l2 revtq_l3 revtq_l4
revtq 1.0000000 0.9916167 0.9938489 0.9905522 0.9972735
revtq_l1 0.9916167 1.0000000 0.9914767 0.9936977 0.9898184
revtq_l2 0.9938489 0.9914767 1.0000000 0.9913489 0.9930152
revtq_l3 0.9905522 0.9936977 0.9913489 1.0000000 0.9906006
revtq_l4 0.9972735 0.9898184 0.9930152 0.9906006 1.0000000
cor(train[,c("revtq_gr","revtq_gr1","revtq_gr2","revtq_gr3", "revtq_gr4")],
use="complete.obs")
revtq_gr revtq_gr1 revtq_gr2 revtq_gr3 revtq_gr4
revtq_gr 1.00000000 -0.32291329 0.06299605 -0.22769442 0.64570015
revtq_gr1 -0.32291329 1.00000000 -0.31885020 0.06146805 -0.21923630
revtq_gr2 0.06299605 -0.31885020 1.00000000 -0.32795121 0.06775742
revtq_gr3 -0.22769442 0.06146805 -0.32795121 1.00000000 -0.31831023
revtq_gr4 0.64570015 -0.21923630 0.06775742 -0.31831023 1.00000000
cor(train[,c("revtq_yoy","revtq_yoy1","revtq_yoy2","revtq_yoy3", "revtq_yoy4")],
use="complete.obs")
revtq_yoy revtq_yoy1 revtq_yoy2 revtq_yoy3 revtq_yoy4
revtq_yoy 1.0000000 0.6554179 0.4127263 0.4196003 0.1760055
revtq_yoy1 0.6554179 1.0000000 0.5751128 0.3665961 0.3515105
revtq_yoy2 0.4127263 0.5751128 1.0000000 0.5875643 0.3683539
revtq_yoy3 0.4196003 0.3665961 0.5875643 1.0000000 0.5668211
revtq_yoy4 0.1760055 0.3515105 0.3683539 0.5668211 1.0000000
cor(train[,c("revtq_d","revtq_d1","revtq_d2","revtq_d3", "revtq_d4")],
use="complete.obs")
revtq_d revtq_d1 revtq_d2 revtq_d3 revtq_d4
revtq_d 1.0000000 -0.6181516 0.3309349 -0.6046998 0.9119911
revtq_d1 -0.6181516 1.0000000 -0.6155259 0.3343317 -0.5849841
revtq_d2 0.3309349 -0.6155259 1.0000000 -0.6191366 0.3165450
revtq_d3 -0.6046998 0.3343317 -0.6191366 1.0000000 -0.5864285
revtq_d4 0.9119911 -0.5849841 0.3165450 -0.5864285 1.0000000
mod1 <- lm(revtq ~ revtq_l1, data=train)
mod2 <- lm(revtq ~ revtq_l1 + revtq_l4, data=train)
mod3 <- lm(revtq ~ revtq_l1 + revtq_l2 + revtq_l3 + revtq_l4 +
revtq_l5 + revtq_l6 + revtq_l7 + revtq_l8, data=train)
mod4 <- lm(revtq ~ (revtq_l1 + revtq_l2 + revtq_l3 + revtq_l4 +
revtq_l5 + revtq_l6 + revtq_l7 + revtq_l8):factor(fqtr),
data=train)
summary(mod1)
Call:
lm(formula = revtq ~ revtq_l1, data = train)
Residuals:
Min 1Q Median 3Q Max
-24438.7 -34.0 -11.7 34.6 15200.5
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 15.639975 13.514877 1.157 0.247
revtq_l1 1.003038 0.001556 644.462 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 1152 on 7676 degrees of freedom
(662 observations deleted due to missingness)
Multiple R-squared: 0.9819, Adjusted R-squared: 0.9819
F-statistic: 4.153e+05 on 1 and 7676 DF, p-value: < 2.2e-16
summary(mod2)
Call:
lm(formula = revtq ~ revtq_l1 + revtq_l4, data = train)
Residuals:
Min 1Q Median 3Q Max
-20245.7 -18.4 -4.4 19.1 9120.8
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5.444986 7.145633 0.762 0.446
revtq_l1 0.231759 0.005610 41.312 <2e-16 ***
revtq_l4 0.815570 0.005858 139.227 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 592.1 on 7274 degrees of freedom
(1063 observations deleted due to missingness)
Multiple R-squared: 0.9954, Adjusted R-squared: 0.9954
F-statistic: 7.94e+05 on 2 and 7274 DF, p-value: < 2.2e-16
summary(mod3)
Call:
lm(formula = revtq ~ revtq_l1 + revtq_l2 + revtq_l3 + revtq_l4 +
revtq_l5 + revtq_l6 + revtq_l7 + revtq_l8, data = train)
Residuals:
Min 1Q Median 3Q Max
-5005.6 -12.9 -3.7 9.3 5876.3
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4.02478 4.37003 0.921 0.3571
revtq_l1 0.77379 0.01229 62.972 < 2e-16 ***
revtq_l2 0.10497 0.01565 6.707 2.16e-11 ***
revtq_l3 -0.03091 0.01538 -2.010 0.0445 *
revtq_l4 0.91982 0.01213 75.800 < 2e-16 ***
revtq_l5 -0.76459 0.01324 -57.749 < 2e-16 ***
revtq_l6 -0.08080 0.01634 -4.945 7.80e-07 ***
revtq_l7 0.01146 0.01594 0.719 0.4721
revtq_l8 0.07924 0.01209 6.554 6.03e-11 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 346 on 6666 degrees of freedom
(1665 observations deleted due to missingness)
Multiple R-squared: 0.9986, Adjusted R-squared: 0.9986
F-statistic: 5.802e+05 on 8 and 6666 DF, p-value: < 2.2e-16
summary(mod4)
Call:
lm(formula = revtq ~ (revtq_l1 + revtq_l2 + revtq_l3 + revtq_l4 +
revtq_l5 + revtq_l6 + revtq_l7 + revtq_l8):factor(fqtr),
data = train)
Residuals:
Min 1Q Median 3Q Max
-6066.6 -13.9 0.1 15.1 4941.1
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.201107 4.004046 -0.050 0.959944
revtq_l1:factor(fqtr)1 0.488584 0.021734 22.480 < 2e-16 ***
revtq_l1:factor(fqtr)2 1.130563 0.023017 49.120 < 2e-16 ***
revtq_l1:factor(fqtr)3 0.774983 0.028727 26.977 < 2e-16 ***
revtq_l1:factor(fqtr)4 0.977353 0.026888 36.349 < 2e-16 ***
revtq_l2:factor(fqtr)1 0.258024 0.035136 7.344 2.33e-13 ***
revtq_l2:factor(fqtr)2 -0.100284 0.024664 -4.066 4.84e-05 ***
revtq_l2:factor(fqtr)3 0.212954 0.039698 5.364 8.40e-08 ***
revtq_l2:factor(fqtr)4 0.266761 0.035226 7.573 4.14e-14 ***
revtq_l3:factor(fqtr)1 0.124187 0.036695 3.384 0.000718 ***
revtq_l3:factor(fqtr)2 -0.042214 0.035787 -1.180 0.238197
revtq_l3:factor(fqtr)3 -0.005758 0.024367 -0.236 0.813194
revtq_l3:factor(fqtr)4 -0.308661 0.038974 -7.920 2.77e-15 ***
revtq_l4:factor(fqtr)1 0.459768 0.038266 12.015 < 2e-16 ***
revtq_l4:factor(fqtr)2 0.684943 0.033366 20.528 < 2e-16 ***
revtq_l4:factor(fqtr)3 0.252169 0.035708 7.062 1.81e-12 ***
revtq_l4:factor(fqtr)4 0.817136 0.017927 45.582 < 2e-16 ***
revtq_l5:factor(fqtr)1 -0.435406 0.023278 -18.704 < 2e-16 ***
revtq_l5:factor(fqtr)2 -0.725000 0.035497 -20.424 < 2e-16 ***
revtq_l5:factor(fqtr)3 -0.160408 0.036733 -4.367 1.28e-05 ***
revtq_l5:factor(fqtr)4 -0.473030 0.033349 -14.184 < 2e-16 ***
revtq_l6:factor(fqtr)1 0.059832 0.034672 1.726 0.084453 .
revtq_l6:factor(fqtr)2 0.154990 0.025368 6.110 1.05e-09 ***
revtq_l6:factor(fqtr)3 -0.156840 0.041147 -3.812 0.000139 ***
revtq_l6:factor(fqtr)4 -0.106082 0.037368 -2.839 0.004541 **
revtq_l7:factor(fqtr)1 0.060031 0.038599 1.555 0.119936
revtq_l7:factor(fqtr)2 0.061381 0.034510 1.779 0.075344 .
revtq_l7:factor(fqtr)3 0.028149 0.025385 1.109 0.267524
revtq_l7:factor(fqtr)4 -0.277337 0.039380 -7.043 2.08e-12 ***
revtq_l8:factor(fqtr)1 -0.016637 0.033568 -0.496 0.620177
revtq_l8:factor(fqtr)2 -0.152379 0.028014 -5.439 5.54e-08 ***
revtq_l8:factor(fqtr)3 0.052208 0.027334 1.910 0.056179 .
revtq_l8:factor(fqtr)4 0.103495 0.015777 6.560 5.78e-11 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 299.7 on 6642 degrees of freedom
(1665 observations deleted due to missingness)
Multiple R-squared: 0.9989, Adjusted R-squared: 0.9989
F-statistic: 1.935e+05 on 32 and 6642 DF, p-value: < 2.2e-16
rmse <- function(v1, v2) {
sqrt(mean((v1 - v2)^2, na.rm=T))
}
mae <- function(v1, v2) {
mean(abs(v1-v2), na.rm=T)
}
models <- list(mod1,mod2,mod3,mod4)
model_names <- c("1 period", "1 and 4 periods", "8 periods", "8 periods w/ quarters")
df_test <- data.frame(adj_r_sq=sapply(models, function(x)summary(x)[["adj.r.squared"]]),
rmse_in=sapply(models, function(x)rmse(train$revtq, predict(x,train))),
mae_in=sapply(models, function(x)mae(train$revtq, predict(x,train))),
rmse_out=sapply(models, function(x)rmse(test$revtq, predict(x,test))),
mae_out=sapply(models, function(x)mae(test$revtq, predict(x,test))))
rownames(df_test) <- model_names
html_df(df_test) # Custom function using knitr and kableExtra
|
adj_r_sq |
rmse_in |
mae_in |
rmse_out |
mae_out |
1 period |
0.9818514 |
1151.3535 |
322.73819 |
2947.3619 |
1252.5196 |
1 and 4 periods |
0.9954393 |
591.9500 |
156.20811 |
1400.3841 |
643.9823 |
8 periods |
0.9985643 |
345.8053 |
94.91083 |
677.6218 |
340.8236 |
8 periods w/ quarters |
0.9989231 |
298.9557 |
91.28056 |
645.5415 |
324.9395 |
test %>%
ggplot(aes(y=revtq,x=predict(mod1,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 1 period model")

test %>%
ggplot(aes(y=revtq,x=predict(mod4,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 8 period X quarter model")

# models
mod1g <- lm(revtq_gr ~ revtq_gr1, data=train)
mod2g <- lm(revtq_gr ~ revtq_gr1 + revtq_gr4, data=train)
mod3g <- lm(revtq_gr ~ revtq_gr1 + revtq_gr2 + revtq_gr3 + revtq_gr4 + revtq_gr5 + revtq_gr6 + revtq_gr7 + revtq_gr8, data=train)
mod4g <- lm(revtq_gr ~ (revtq_gr1 + revtq_gr2 + revtq_gr3 + revtq_gr4 + revtq_gr5 + revtq_gr6 + revtq_gr7 + revtq_gr8):factor(fqtr), data=train)
models <- list(mod1g, mod2g, mod3g, mod4g)
model_names <- c("1 period", "1 and 4 periods", "8 periods", "8 periods w/ quarters")
df_test <- data.frame(adj_r_sq=sapply(models, function(x)summary(x)[["adj.r.squared"]]),
rmse_in=sapply(models, function(x)rmse(train$revtq, (1+predict(x,train))*train$revtq_l1)),
mae_in=sapply(models, function(x)mae(train$revtq, (1+predict(x,train))*train$revtq_l1)),
rmse_out=sapply(models, function(x)rmse(test$revtq, (1+predict(x,test))*test$revtq_l1)),
mae_out=sapply(models, function(x)mae(test$revtq, (1+predict(x,test))*test$revtq_l1)))
rownames(df_test) <- model_names
html_df(df_test)
|
adj_r_sq |
rmse_in |
mae_in |
rmse_out |
mae_out |
1 period |
0.0910390 |
1106.3730 |
308.48331 |
3374.728 |
1397.6541 |
1 and 4 periods |
0.4398456 |
530.6444 |
154.15086 |
1447.035 |
679.3536 |
8 periods |
0.6761666 |
456.2551 |
123.34075 |
1254.201 |
584.9709 |
8 periods w/ quarters |
0.7758834 |
378.4082 |
98.45751 |
1015.971 |
436.1522 |
test %>%
ggplot(aes(y=revtq,x=(1+predict(mod1g,test))*test$revtq_l1, color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 1 period model")

test %>%
ggplot(aes(y=revtq,x=(1+predict(mod4g,test))*test$revtq_l1, color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 8 period X quarter model")

# models
mod1y <- lm(revtq_yoy ~ revtq_yoy1, data=train)
mod2y <- lm(revtq_yoy ~ revtq_yoy1 + revtq_yoy4, data=train)
mod3y <- lm(revtq_yoy ~ revtq_yoy1 + revtq_yoy2 + revtq_yoy3 + revtq_yoy4 + revtq_yoy5 + revtq_yoy6 + revtq_yoy7 + revtq_yoy8, data=train)
mod4y <- lm(revtq_gr ~ (revtq_yoy1 + revtq_yoy2 + revtq_yoy3 + revtq_yoy4 + revtq_yoy5 + revtq_yoy6 + revtq_yoy7 + revtq_yoy8):factor(fqtr), data=train)
models <- list(mod1y, mod2y, mod3y, mod4y)
model_names <- c("1 period", "1 and 4 periods", "8 periods", "8 periods w/ quarters")
df_test <- data.frame(adj_r_sq=sapply(models, function(x)summary(x)[["adj.r.squared"]]),
rmse_in=sapply(models, function(x)rmse(train$revtq, (1+predict(x,train))*train$revtq_l4)),
mae_in=sapply(models, function(x)mae(train$revtq, (1+predict(x,train))*train$revtq_l4)),
rmse_out=sapply(models, function(x)rmse(test$revtq, (1+predict(x,test))*test$revtq_l4)),
mae_out=sapply(models, function(x)mae(test$revtq, (1+predict(x,test))*test$revtq_l4)))
rownames(df_test) <- model_names
html_df(df_test)
|
adj_r_sq |
rmse_in |
mae_in |
rmse_out |
mae_out |
1 period |
0.4370372 |
513.3264 |
129.2309 |
1867.4957 |
798.0327 |
1 and 4 periods |
0.5392281 |
487.6441 |
126.6012 |
1677.4003 |
731.2841 |
8 periods |
0.5398870 |
384.2923 |
101.0104 |
822.0065 |
403.5445 |
8 periods w/ quarters |
0.1563169 |
714.4285 |
195.3204 |
1231.8436 |
617.2989 |
test %>%
ggplot(aes(y=revtq,x=(1+predict(mod1y,test))*test$revtq_l4, color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 1 period model")

test %>%
ggplot(aes(y=revtq,x=(1+predict(mod3y,test))*test$revtq_l4, color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 8 period model")

# models
mod1d <- lm(revtq_d ~ revtq_d1, data=train)
mod2d <- lm(revtq_d ~ revtq_d1 + revtq_d4, data=train)
mod3d <- lm(revtq_d ~ revtq_d1 + revtq_d2 + revtq_d3 + revtq_d4 + revtq_d5 + revtq_d6 + revtq_d7 + revtq_d8, data=train)
mod4d <- lm(revtq_d ~ (revtq_d1 + revtq_d2 + revtq_d3 + revtq_d4 + revtq_d5 + revtq_d6 + revtq_d7 + revtq_d8):factor(fqtr), data=train)
models <- list(mod1d, mod2d, mod3d, mod4d)
model_names <- c("1 period", "1 and 4 periods", "8 periods", "8 periods w/ quarters")
df_test <- data.frame(adj_r_sq=sapply(models, function(x)summary(x)[["adj.r.squared"]]),
rmse_in=sapply(models, function(x)rmse(train$revtq, predict(x,train)+train$revtq_l1)),
mae_in=sapply(models, function(x)mae(train$revtq, predict(x,train)+train$revtq_l1)),
rmse_out=sapply(models, function(x)rmse(test$revtq, predict(x,test)+test$revtq_l1)),
mae_out=sapply(models, function(x)mae(test$revtq, predict(x,test)+test$revtq_l1)))
rownames(df_test) <- model_names
html_df(df_test)
|
adj_r_sq |
rmse_in |
mae_in |
rmse_out |
mae_out |
1 period |
0.3532044 |
896.7969 |
287.77940 |
2252.7605 |
1022.0960 |
1 and 4 periods |
0.8425348 |
454.8651 |
115.52694 |
734.8120 |
377.5281 |
8 periods |
0.9220849 |
333.0054 |
95.95924 |
651.4967 |
320.0567 |
8 periods w/ quarters |
0.9397434 |
292.3102 |
86.95563 |
659.4412 |
319.7305 |
test %>%
ggplot(aes(y=revtq,x=predict(mod1d,test)+test$revtq_l1, color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 1 period model")

test %>%
ggplot(aes(y=revtq,x=predict(mod4d,test)+test$revtq_l1, color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue") +
xlab("Prediction: 8 period X quarter model")

# models
mod1g <- lm(revtq_gr ~ revtq_gr1, data=train)
mod2g <- lm(revtq_gr ~ revtq_gr1 + revtq_gr4, data=train)
mod3g <- lm(revtq_gr ~ revtq_gr1 + revtq_gr2 + revtq_gr3 + revtq_gr4 + revtq_gr5 + revtq_gr6 + revtq_gr7 + revtq_gr8, data=train)
mod4g <- lm(revtq_gr ~ (revtq_gr1 + revtq_gr2 + revtq_gr3 + revtq_gr4 + revtq_gr5 + revtq_gr6 + revtq_gr7 + revtq_gr8):factor(fqtr), data=train)
models <- list(mod1g, mod2g, mod3g, mod4g)
model_names <- c("1 period", "1 and 4 periods", "8 periods", "8 periods w/ quarters")
df_test <- data.frame(adj_r_sq=sapply(models, function(x)summary(x)[["adj.r.squared"]]),
rmse_in=sapply(models, function(x)rmse(train$revtq_gr, predict(x,train))),
mae_in=sapply(models, function(x)mae(train$revtq_gr, predict(x,train))),
rmse_out=sapply(models, function(x)rmse(test$revtq_gr, predict(x,test))),
mae_out=sapply(models, function(x)mae(test$revtq_gr, predict(x,test))))
rownames(df_test) <- model_names
html_df(df_test)
|
adj_r_sq |
rmse_in |
mae_in |
rmse_out |
mae_out |
1 period |
0.0910390 |
0.3509269 |
0.2105219 |
0.2257396 |
0.1750580 |
1 and 4 periods |
0.4398456 |
0.2681899 |
0.1132003 |
0.1597771 |
0.0998087 |
8 periods |
0.6761666 |
0.1761825 |
0.0867347 |
0.1545298 |
0.0845826 |
8 periods w/ quarters |
0.7758834 |
0.1462979 |
0.0765792 |
0.1459460 |
0.0703554 |
test %>%
ggplot(aes(y=revtq_gr,x=predict(mod1g,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue growth") +
xlab("Prediction: 1 period model")

test %>%
ggplot(aes(y=revtq_gr,x=predict(mod4g,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue growth") +
xlab("Prediction: 8 period X quarter model")

# models
mod1y <- lm(revtq_yoy ~ revtq_yoy1, data=train)
mod2y <- lm(revtq_yoy ~ revtq_yoy1 + revtq_yoy4, data=train)
mod3y <- lm(revtq_yoy ~ revtq_yoy1 + revtq_yoy2 + revtq_yoy3 + revtq_yoy4 + revtq_yoy5 + revtq_yoy6 + revtq_yoy7 + revtq_yoy8, data=train)
mod4y <- lm(revtq_gr ~ (revtq_yoy1 + revtq_yoy2 + revtq_yoy3 + revtq_yoy4 + revtq_yoy5 + revtq_yoy6 + revtq_yoy7 + revtq_yoy8):factor(fqtr), data=train)
models <- list(mod1y, mod2y, mod3y, mod4y)
model_names <- c("1 period", "1 and 4 periods", "8 periods", "8 periods w/ quarters")
df_test <- data.frame(adj_r_sq=sapply(models, function(x)summary(x)[["adj.r.squared"]]),
rmse_in=sapply(models, function(x)rmse(train$revtq_yoy, predict(x,train))),
mae_in=sapply(models, function(x)mae(train$revtq_yoy, predict(x,train))),
rmse_out=sapply(models, function(x)rmse(test$revtq_yoy, predict(x,test))),
mae_out=sapply(models, function(x)mae(test$revtq_yoy, predict(x,test))))
rownames(df_test) <- model_names
html_df(df_test)
|
adj_r_sq |
rmse_in |
mae_in |
rmse_out |
mae_out |
1 period |
0.4370372 |
0.3116645 |
0.1114610 |
0.1515638 |
0.0942544 |
1 and 4 periods |
0.5392281 |
0.2451749 |
0.1015699 |
0.1498755 |
0.0896079 |
8 periods |
0.5398870 |
0.1928940 |
0.0764447 |
0.1346238 |
0.0658011 |
8 periods w/ quarters |
0.1563169 |
0.3006075 |
0.1402156 |
0.1841025 |
0.0963205 |
test %>%
ggplot(aes(y=revtq_yoy,x=predict(mod1y,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual year over year revenue growth") +
xlab("Prediction: 1 period model")

test %>%
ggplot(aes(y=revtq_yoy,x=predict(mod3y,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual year over year revenue growth") +
xlab("Prediction: 8 period model")

# models
mod1d <- lm(revtq_d ~ revtq_d1, data=train)
mod2d <- lm(revtq_d ~ revtq_d1 + revtq_d4, data=train)
mod3d <- lm(revtq_d ~ revtq_d1 + revtq_d2 + revtq_d3 + revtq_d4 + revtq_d5 + revtq_d6 + revtq_d7 + revtq_d8, data=train)
mod4d <- lm(revtq_d ~ (revtq_d1 + revtq_d2 + revtq_d3 + revtq_d4 + revtq_d5 + revtq_d6 + revtq_d7 + revtq_d8):factor(fqtr), data=train)
models <- list(mod1d, mod2d, mod3d, mod4d)
model_names <- c("1 period", "1 and 4 periods", "8 periods", "8 periods w/ quarters")
df_test <- data.frame(adj_r_sq=sapply(models, function(x)summary(x)[["adj.r.squared"]]),
rmse_in=sapply(models, function(x)rmse(train$revtq_d, predict(x,train))),
mae_in=sapply(models, function(x)mae(train$revtq_d, predict(x,train))),
rmse_out=sapply(models, function(x)rmse(test$revtq_d, predict(x,test))),
mae_out=sapply(models, function(x)mae(test$revtq_d, predict(x,test))))
rownames(df_test) <- model_names
html_df(df_test)
|
adj_r_sq |
rmse_in |
mae_in |
rmse_out |
mae_out |
1 period |
0.3532044 |
896.7969 |
287.77940 |
2252.7605 |
1022.0960 |
1 and 4 periods |
0.8425348 |
454.8651 |
115.52694 |
734.8120 |
377.5281 |
8 periods |
0.9220849 |
333.0054 |
95.95924 |
651.4967 |
320.0567 |
8 periods w/ quarters |
0.9397434 |
292.3102 |
86.95563 |
659.4412 |
319.7305 |
test %>%
ggplot(aes(y=revtq_d,x=predict(mod1d,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue first difference") +
xlab("Prediction: 1 period model")

test %>%
ggplot(aes(y=revtq_d,x=predict(mod4d,test), color=factor(fqtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue first difference") +
xlab("Prediction: 8 period X quarter model")

LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gMyINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KTm90ZSB0aGF0IHRoZSBkaXJlY3RvcmllcyB1c2VkIHRvIHN0b3JlIGRhdGEgYXJlIGxpa2VseSBkaWZmZXJlbnQgb24geW91ciBjb21wdXRlciwgYW5kIHN1Y2ggcmVmZXJlbmNlcyB3aWxsIG5lZWQgdG8gYmUgY2hhbmdlZCBiZWZvcmUgdXNpbmcgYW55IHN1Y2ggY29kZS4NCg0KYGBge3IgaGVscGVycywgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpodG1sX2RmIDwtIGZ1bmN0aW9uKHRleHQsIGNvbHM9TlVMTCwgY29sMT1GQUxTRSwgZnVsbD1GKSB7DQogIGlmKCFsZW5ndGgoY29scykpIHsNCiAgICBjb2xzPWNvbG5hbWVzKHRleHQpDQogIH0NCiAgaWYoIWNvbDEpIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpDQogIH0gZWxzZSB7DQogICAga2FibGUodGV4dCwiaHRtbCIsIGNvbC5uYW1lcyA9IGNvbHMsIGFsaWduID0gYygibCIscmVwKCdjJyxsZW5ndGgoY29scyktMSkpKSAlPiUNCiAgICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwiaG92ZXIiKSwgZnVsbF93aWR0aD1mdWxsKSAlPiUNCiAgICAgIGNvbHVtbl9zcGVjKDEsYm9sZD1UKQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmRmIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fMy0xLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQp3bXQgPC0gZmlsdGVyKGRmLCB0aWMgPT0gIldNVCIpDQoNCiMgbG9hZCBpbiByZWxldmFudCBkYXRhIGZyb20gU2Vzc2lvbiAyDQpsb2FkKCIuLi8uLi9EYXRhL1Nlc3Npb25fMl9leHBvcnQuUkRhdGEiKQ0KYGBgDQoNCmBgYHtyfQ0KZXhwZWN0YXRpb25zIDwtIHJlYWRfY3N2KCIuLi8uLi9EYXRhL2dlbmVyYWwtYnVzaW5lc3MtZXhwZWN0YXRpb25zLWJ5LWRldGFpbGVkLXNlcnZpY2VzLWluZHVzdHJ5LXF1YXJ0ZXJseS5jc3YiKSAlPiUNCiAgbXV0YXRlKHllYXIgPSBhcy5udW1lcmljKHN1YnN0cihxdWFydGVyLCAxLCA0KSkpICU+JSAgICAjIHNwbGl0IG91dCB5ZWFyDQogIG11dGF0ZShxdWFydGVyID0gYXMubnVtZXJpYyhzdWJzdHIocXVhcnRlciwgNywgNykpKSAlPiUgIyBzcGxpdCBvdXQgcXVhcnRlcg0KICBtdXRhdGUodmFsdWUgPSBhcy5udW1lcmljKHZhbHVlKSkgICAgICAgICAgICAgICAgICAgICAgICMgRW5zdWUgdmFsdWUgaXMgbnVtZXJpYw0KYGBgDQoNCmBgYHtyfQ0KIyBleHRyYWN0IG91dCBRMSwgZmluYW5jZSBvbmx5DQpleHBlY3RhdGlvbnNfYXZnIDwtIGV4cGVjdGF0aW9ucyAlPiUNCiAgZmlsdGVyKHF1YXJ0ZXIgPT0gMSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgdGhlIGZpcnN0IHF1YXJ0ZXINCiAgICAgICAgIGxldmVsXzIgPT0gIkZpbmFuY2lhbCAmIEluc3VyYW5jZSIpICU+JSAgICAgIyBLZWVwIG9ubHkgZmluYW5jaWFsIHJlc3BvbnNlcw0KICBncm91cF9ieSh5ZWFyKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEdyb3VwIGRhdGEgYnkgeWVhcg0KICBtdXRhdGUoZmluX3NlbnRpbWVudD1tZWFuKHZhbHVlLCBuYS5ybT1UUlVFKSkgJT4lICAjIENhbGN1bGF0ZSBhdmVyYWdlDQogIHNsaWNlKDEpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVGFrZSBvbmx5IDEgcm93IHBlciBncm91cA0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShEVCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GfQ0KZXhwZWN0YXRpb25zICU+JQ0KICBhcnJhbmdlKGxldmVsXzIsIGxldmVsXzMsIGRlc2MoeWVhcikpICU+JSAgIyBzb3J0IHRoZSBkYXRhDQogIHNlbGVjdCh5ZWFyLCBxdWFydGVyLCBsZXZlbF8yLCBsZXZlbF8zLCB2YWx1ZSkgJT4lICAjIGtlZXAgb25seSB0aGVzZSB2YXJpYWJsZXMNCiAgZGF0YXRhYmxlKG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSA1KSwgcm93bmFtZXM9RkFMU0UpICAjIGRpc3BsYXkgdXNpbmcgRFQNCmBgYA0KDQpgYGB7cn0NCiMgc3Vic2V0IG91dCBvdXIgU2luZ2Fwb3JlYW4gZGF0YSwgc2luY2Ugb3VyIG1hY3JvIGRhdGEgaXMgU2luZ2Fwb3JlLXNwZWNpZmljDQpkZl9TRyA8LSBkZl9jbGVhbiAlPiUgZmlsdGVyKGZpYyA9PSAiU0dQIikNCg0KIyBDcmVhdGUgeWVhciBpbiBkZl9TRyAoZGF0ZSBpcyBnaXZlbiBieSBkYXRhZGF0ZSBhcyBZWVlZTU1ERCkNCmRmX1NHJHllYXIgPSByb3VuZChkZl9TRyRkYXRhZGF0ZSAvIDEwMDAwLCBkaWdpdHM9MCkNCg0KIyBDb21iaW5lIGRhdGFzZXRzDQojIE5vdGljZSBob3cgaXQgYXV0b21hdGljYWxseSBmaWd1cmVzIG91dCB0byBqb2luIGJ5ICJ5ZWFyIg0KZGZfU0dfbWFjcm8gPC0gbGVmdF9qb2luKGRmX1NHLCBleHBlY3RhdGlvbnNfYXZnWyxjKCJ5ZWFyIiwiZmluX3NlbnRpbWVudCIpXSkNCmBgYA0KDQpgYGB7cn0NCm1hY3JvMSA8LSBsbShyZXZ0X2xlYWQgfiByZXZ0ICsgYWN0ICsgY2hlICsgbGN0ICsgZHAgKyBlYml0ICsgZmluX3NlbnRpbWVudCwNCiAgICAgICAgICAgICBkYXRhPWRmX1NHX21hY3JvKQ0KbGlicmFyeShicm9vbSkNCnRpZHkobWFjcm8xKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NH0NCmRmX1NHX21hY3JvICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dF9sZWFkLA0KICAgICAgICAgICAgIHg9ZmluX3NlbnRpbWVudCkpICsgDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NH0NCmRmX1NHX21hY3JvICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dF9sZWFkLA0KICAgIHg9c2NhbGUoZmluX3NlbnRpbWVudCkgKiByZXZ0KSkgKyANCiAgZ2VvbV9wb2ludCgpDQpgYGANCg0KYGBge3J9DQojIFNjYWxlIGNyZWF0ZXMgei1zY29yZXMsIGJ1dCByZXR1cm5zIGEgbWF0cml4IGJ5IGRlZmF1bHQuICBbLDFdIGdpdmVzIGEgdmVjdG9yDQpkZl9TR19tYWNybyRmaW5fc2VudF9zY2FsZWQgPC0gc2NhbGUoZGZfU0dfbWFjcm8kZmluX3NlbnRpbWVudClbLDFdDQptYWNybzMgPC0NCiAgbG0ocmV2dF9sZWFkIH4gcmV2dCArIGFjdCArIGNoZSArIGxjdCArIGRwICsgZWJpdCArIGZpbl9zZW50X3NjYWxlZDpyZXZ0LA0KICAgICBkYXRhPWRmX1NHX21hY3JvKQ0KdGlkeShtYWNybzMpDQpnbGFuY2UobWFjcm8zKQ0KYGBgDQoNCmBgYHtyfQ0KYmFzZWxpbmUgPC0NCiAgbG0ocmV2dF9sZWFkIH4gcmV2dCArIGFjdCArIGNoZSArIGxjdCArIGRwICsgZWJpdCwNCiAgICAgZGF0YT1kZl9TR19tYWNyb1shaXMubmEoZGZfU0dfbWFjcm8kZmluX3NlbnRpbWVudCksXSkNCmdsYW5jZShiYXNlbGluZSkNCmdsYW5jZShtYWNybzMpDQpgYGANCg0KYGBge3J9DQphbm92YShiYXNlbGluZSwgbWFjcm8zLCB0ZXN0PSJDaGlzcSIpDQpgYGANCg0KYGBge3J9DQpyX3NkIDwtIHJvdW5kKHNkKGRmX1NHX21hY3JvJGZpbl9zZW50aW1lbnQsIG5hLnJtPVQpLDEpDQpyX21pbiA8LSBtaW4oZGZfU0dfbWFjcm8kZmluX3NlbnRpbWVudCwgbmEucm09VCkNCnJfbWF4IDwtIG1heChkZl9TR19tYWNybyRmaW5fc2VudGltZW50LCBuYS5ybT1UKQ0KcmV2IDwtIG1hY3JvMyRjb2VmZmljaWVudHNbWyJyZXZ0OmZpbl9zZW50X3NjYWxlZCJdXQ0Kcl9yZXYgPSByb3VuZCgxMDAgKiByZXYsMCkNCnJldl9taW4gPC0gcm91bmQoKHJfbWluIC8gcl9zZCAqIHJldiArIHJldikqMTAwLDEpDQpyZXZfbWF4IDwtIHJvdW5kKChyX21heCAvIHJfc2QgKiByZXYgKyByZXYpKjEwMCwxKQ0KYGBgDQoNCmBgYHtyfQ0KcF91b2wgPC0gcHJlZGljdChmb3JlY2FzdDIsIHVvbFt1b2wkZnllYXI9PTIwMTcsXSkNCnBfYmFzZSA8LSBwcmVkaWN0KGJhc2VsaW5lLA0KICBkZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyPT0yMDE3LF0pDQpwX21hY3JvIDwtIHByZWRpY3QobWFjcm8zLA0KICBkZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyPT0yMDE3LF0pDQpwX3dvcmxkIDwtIHByZWRpY3QoZm9yZWNhc3Q0LA0KICBkZl9jbGVhbltkZl9jbGVhbiRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX2NsZWFuJGZ5ZWFyPT0yMDE3LF0pDQpwcmVkcyA8LSBjKHBfdW9sLCBwX2Jhc2UsIHBfbWFjcm8sIHBfd29ybGQpDQpuYW1lcyhwcmVkcykgPC0gYygiVU9MIDIwMTggVU9MIiwgIlVPTCAyMDE4IEJhc2UiLCAiVU9MIDIwMTggTWFjcm8iLA0KICAgICAgICAgICAgICAgICAgIlVPTCAyMDE4IFdvcmxkIikNCnByZWRzDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9Niwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpsaWJyYXJ5KHBsb3RseSkNCmRmX1NHX21hY3JvJHByZWRfYmFzZSA8LSBwcmVkaWN0KGJhc2VsaW5lLCBkZl9TR19tYWNybykNCmRmX1NHX21hY3JvJHByZWRfbWFjcm8gPC0gcHJlZGljdChtYWNybzMsIGRmX1NHX21hY3JvKQ0KZGZfY2xlYW4kcHJlZF93b3JsZCA8LSBwcmVkaWN0KGZvcmVjYXN0NCwgZGZfY2xlYW4pDQp1b2wkcHJlZF91b2wgPC0gcHJlZGljdChmb3JlY2FzdDIsIHVvbCkNCmRmX3ByZWRzIDwtIGRhdGEuZnJhbWUocHJlZHM9cHJlZHMsIGZ5ZWFyPWMoMjAxOCwyMDE4LDIwMTgsMjAxOCksIG1vZGVsPWMoIlVPTCBvbmx5IiwgIkJhc2UiLCAiTWFjcm8iLCAiV29ybGQiKSkNCnBsb3QgPC0gZ2dwbG90KCkgKyANCiAgZ2VvbV9wb2ludChkYXRhPWRmX1NHX21hY3JvW2RmX1NHX21hY3JvJGlzaW49PSJTRzFTODMwMDIzNDkiICYgZGZfU0dfbWFjcm8kZnllYXIgPCAyMDE3LF0sIGFlcyh5PXJldnRfbGVhZCx4PWZ5ZWFyLCBjb2xvcj0iQWN0dWFsIikpICsNCiAgZ2VvbV9saW5lKGRhdGE9ZGZfU0dfbWFjcm9bZGZfU0dfbWFjcm8kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9TR19tYWNybyRmeWVhciA8IDIwMTcsXSwgYWVzKHk9cmV2dF9sZWFkLHg9ZnllYXIsIGNvbG9yPSJBY3R1YWwiKSkgKyANCiAgZ2VvbV9wb2ludChkYXRhPXVvbFt1b2wkZnllYXIgPCAyMDE3LF0sIGFlcyh5PXByZWRfdW9sLHg9ZnllYXIsIGNvbG9yPSJVT0wgb25seSIpKSArDQogIGdlb21fbGluZShkYXRhPXVvbFt1b2wkZnllYXIgPCAyMDE3LF0sIGFlcyh5PXByZWRfdW9sLHg9ZnllYXIsIGNvbG9yPSJVT0wgb25seSIpKSArDQogIGdlb21fcG9pbnQoZGF0YT1kZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX2Jhc2UseD1meWVhciwgY29sb3I9IkJhc2UiKSkgKw0KICBnZW9tX2xpbmUoZGF0YT1kZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX2Jhc2UseD1meWVhciwgY29sb3I9IkJhc2UiKSkgKw0KICBnZW9tX3BvaW50KGRhdGE9ZGZfU0dfbWFjcm9bZGZfU0dfbWFjcm8kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9TR19tYWNybyRmeWVhciA8IDIwMTcsXSwgYWVzKHk9cHJlZF9tYWNybyx4PWZ5ZWFyLCBjb2xvcj0iTWFjcm8iKSkgKw0KICBnZW9tX2xpbmUoZGF0YT1kZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX21hY3JvLHg9ZnllYXIsIGNvbG9yPSJNYWNybyIpKSArIA0KICBnZW9tX3BvaW50KGRhdGE9ZGZfY2xlYW5bZGZfY2xlYW4kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9jbGVhbiRmeWVhciA8IDIwMTcsXSwgYWVzKHk9cHJlZF93b3JsZCx4PWZ5ZWFyLCBjb2xvcj0iV29ybGQiKSkgKw0KICBnZW9tX2xpbmUoZGF0YT1kZl9jbGVhbltkZl9jbGVhbiRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX2NsZWFuJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX3dvcmxkLHg9ZnllYXIsIGNvbG9yPSJXb3JsZCIpKSArIA0KICBnZW9tX3BvaW50KGRhdGE9ZGZfcHJlZHMsIGFlcyh5PXByZWRzLCB4PWZ5ZWFyLCBjb2xvcj1tb2RlbCksIHNpemU9MS41LCBzaGFwZT0xOCkNCmdncGxvdGx5KHBsb3QpDQpgYGANCg0KYGBge3J9DQphY3R1YWxfc2VyaWVzIDwtIGRmX1NHX21hY3JvW2RmX1NHX21hY3JvJGlzaW49PSJTRzFTODMwMDIzNDkiICYgZGZfU0dfbWFjcm8kZnllYXIgPCAyMDE3LF0kcmV2dF9sZWFkDQp1b2xfc2VyaWVzIDwtIHVvbFt1b2wkZnllYXIgPCAyMDE3LF0kcHJlZF91b2wNCmJhc2Vfc2VyaWVzIDwtIGRmX1NHX21hY3JvW2RmX1NHX21hY3JvJGlzaW49PSJTRzFTODMwMDIzNDkiICYgZGZfU0dfbWFjcm8kZnllYXIgPCAyMDE3LF0kcHJlZF9iYXNlDQptYWNyb19zZXJpZXMgPC0gZGZfU0dfbWFjcm9bZGZfU0dfbWFjcm8kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9TR19tYWNybyRmeWVhciA8IDIwMTcsXSRwcmVkX21hY3JvDQp3b3JsZF9zZXJpZXMgPC0gZGZfY2xlYW5bZGZfY2xlYW4kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9jbGVhbiRmeWVhciA8IDIwMTcsXSRwcmVkX3dvcmxkDQpgYGANCg0KYGBge3J9DQojIHNlcmllcyB2ZWN0b3JzIGNhbGN1bGF0ZWQgaGVyZSAtLSBTZWUgYXBwZW5kaXgNCnJtc2UgPC0gZnVuY3Rpb24odjEsIHYyKSB7DQogIHNxcnQobWVhbigodjEgLSB2MileMiwgbmEucm09VCkpDQp9DQoNCnJtc2UgPC0gYyhybXNlKGFjdHVhbF9zZXJpZXMsIHVvbF9zZXJpZXMpLCBybXNlKGFjdHVhbF9zZXJpZXMsIGJhc2Vfc2VyaWVzKSwNCiAgICAgICAgICBybXNlKGFjdHVhbF9zZXJpZXMsIG1hY3JvX3NlcmllcyksIHJtc2UoYWN0dWFsX3Nlcmllcywgd29ybGRfc2VyaWVzKSkNCm5hbWVzKHJtc2UpIDwtIGMoIlVPTCAyMDE4IFVPTCIsICJVT0wgMjAxOCBCYXNlIiwgIlVPTCAyMDE4IE1hY3JvIiwgIlVPTCAyMDE4IFdvcmxkIikNCnJtc2UNCmBgYA0KDQpgYGB7cn0NCnByZWRzDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KbGlicmFyeSh0aWR5dmVyc2UpICAjIEFzIGFsd2F5cw0KbGlicmFyeShwbG90bHkpICAjIGludGVyYWN0aXZlIGdyYXBocw0KbGlicmFyeShsdWJyaWRhdGUpICAjIGltcG9ydCBzb21lIHNlbnNpYmxlIGRhdGUgZnVuY3Rpb25zDQoNCiMgR2VuZXJhdGUgcXVhcnRlciBvdmVyIHF1YXJ0ZXIgZ3Jvd3RoICJyZXZ0cV9nciINCmRmIDwtIGRmICU+JSBncm91cF9ieShndmtleSkgJT4lIG11dGF0ZShyZXZ0cV9ncj1yZXZ0cSAvIGxhZyhyZXZ0cSkgLSAxKSAlPiUgdW5ncm91cCgpDQoNCiMgR2VuZXJhdGUgeWVhci1vdmVyLXllYXIgZ3Jvd3RoICJyZXZ0cV95b3kiDQpkZiA8LSBkZiAlPiUgZ3JvdXBfYnkoZ3ZrZXkpICU+JSBtdXRhdGUocmV2dHFfeW95PXJldnRxIC8gbGFnKHJldnRxLCA0KSAtIDEpICU+JSB1bmdyb3VwKCkNCg0KIyBHZW5lcmF0ZSBmaXJzdCBkaWZmZXJlbmNlICJyZXZ0cV9kIg0KZGYgPC0gZGYgJT4lIGdyb3VwX2J5KGd2a2V5KSAlPiUgbXV0YXRlKHJldnRxX2Q9cmV2dHEgLSBsYWcocmV2dHEpKSAlPiUgdW5ncm91cCgpDQoNCiMgR2VuZXJhdGUgYSBwcm9wZXIgZGF0ZQ0KIyBEYXRlIHdhcyBZWU1NRERzMTA6IFlZWVkvTU0vREQsIGNhbiBiZSBjb252ZXJ0ZWQgZnJvbSB0ZXh0IHRvIGRhdGUgZWFzaWx5DQpkZiRkYXRlIDwtIGFzLkRhdGUoZGYkZGF0YWRhdGUpICAjIEJ1aWx0IGluIHRvIFINCmBgYA0KDQpgYGB7cn0NCmh0bWxfZGYoaGVhZChkZlssYygiY29ubSIsImRhdGUiLCJyZXZ0cSIsInJldnRxX2dyIiwgInJldnRxX3lveSIsICJyZXZ0cV9kIildKSkNCg0KaGVhZChkZlssYygiY29ubSIsImRhdGUiLCAiZGF0YWRhdGUiKV0pDQpgYGANCg0KYGBge3J9DQojIEN1c3RvbSBGdW5jdGlvbiB0byBnZW5lcmF0ZSBhIHNlcmllcyBvZiBsYWdzDQpsaWJyYXJ5KHJsYW5nKQ0KbXVsdGlfbGFnIDwtIGZ1bmN0aW9uKGRmLCBsYWdzLCB2YXIsIHBvc3RmaXg9IiIpIHsNCiAgdmFyIDwtIGVucXVvKHZhcikNCiAgcXVvc3VyZXMgPC0gbWFwKGxhZ3MsIH5xdW8obGFnKCEhdmFyLCAhIS54KSkpICU+JQ0KICAgIHNldF9uYW1lcyhwYXN0ZTAocXVvX3RleHQodmFyKSwgcG9zdGZpeCwgbGFncykpDQogIA0KICByZXR1cm4obXV0YXRlKGdyb3VwX2J5KGRmLCBndmtleSksICEhIXF1b3N1cmVzKSkNCn0NCg0KIyBHZW5lcmF0ZSBsYWdzICJyZXZ0cV9sIyINCmRmIDwtIG11bHRpX2xhZyhkZiwgMTo4LCByZXZ0cSwgIl9sIikNCg0KIyBHZW5lcmF0ZSBjaGFuZ2VzICJyZXZ0cV9nciMiDQpkZiA8LSBtdWx0aV9sYWcoZGYsIDE6OCwgcmV2dHFfZ3IpDQoNCiMgR2VuZXJhdGUgeWVhci1vdmVyLXllYXIgY2hhbmdlcyAicmV2dHFfeW95IyINCmRmIDwtIG11bHRpX2xhZyhkZiwgMTo4LCByZXZ0cV95b3kpDQoNCiMgR2VuZXJhdGUgZmlyc3QgZGlmZmVyZW5jZXMgInJldnRxX2QjIg0KZGYgPC0gbXVsdGlfbGFnKGRmLCAxOjgsIHJldnRxX2QpDQoNCiMgRXF1aXZhbGVudCBicnV0ZSBmb3JjZSBjb2RlIGZvciB0aGlzIGlzIGluIHRoZSBhcHBlbmRpeA0KYGBgDQoNCmBgYHtyfQ0KaHRtbF9kZihoZWFkKGRmWyxjKCJjb25tIiwiZGF0ZSIsInJldnRxIiwicmV2dHFfbDEiLCAicmV2dHFfbDIiLCAicmV2dHFfbDMiLCAicmV2dHFfbDQiKV0pKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDbGVhbiB0aGUgZGF0YTogUmVwbGFjZSBOYU4sIEluZiwgYW5kIC1JbmYgd2l0aCBOQQ0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBsaXN0KH5yZXBsYWNlKC4sICFpcy5maW5pdGUoLiksIE5BKSkpDQoNCiMgU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhDQojIFRyYWluaW5nIGRhdGE6IFdlJ2xsIHVzZSBkYXRhIHJlbGVhc2VkIGJlZm9yZSAyMDE1DQp0cmFpbiA8LSBmaWx0ZXIoZGYsIHllYXIoZGF0ZSkgPCAyMDE1KQ0KDQojIFRlc3RpbmcgZGF0YTogV2UnbGwgdXNlIGRhdGEgcmVsZWFzZWQgMjAxNSB0aHJvdWdoIDIwMTgNCnRlc3QgPC0gZmlsdGVyKGRmLCB5ZWFyKGRhdGUpID49IDIwMTUpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0Kc3VtbWFyeShkZlssYygicmV2dHEiLCJyZXZ0cV9nciIsInJldnRxX3lveSIsICJyZXZ0cV9kIiwiZnF0ciIpXSkNCmBgYA0KDQpgYGB7cn0NCiMgVGhlc2UgZnVuY3Rpb25zIGFyZSBhIGJpdCB1Z2x5LCBidXQgY2FuIGNvbnN0cnVjdCBtYW55IGNoYXJ0cyBxdWlja2x5DQojIGV2YWwocGFyc2UodGV4dD12YXIpKSBpcyBqdXN0IGEgd2F5IHRvIGNvbnZlcnQgdGhlIHN0cmluZyBuYW1lIHRvIGEgdmFyaWFibGUgcmVmZXJlbmNlDQojIERlbnNpdHkgcGxvdCBmb3IgMXN0IHRvIDk5dGggcGVyY2VudGlsZSBvZiBkYXRhDQpwbHRfZGlzdCA8LSBmdW5jdGlvbihkZix2YXIpIHsNCiAgZGYgJT4lDQogICAgZmlsdGVyKGV2YWwocGFyc2UodGV4dD12YXIpKSA8IHF1YW50aWxlKGV2YWwocGFyc2UodGV4dD12YXIpKSwwLjk5LCBuYS5ybT1UUlVFKSwNCiAgICAgICAgICAgZXZhbChwYXJzZSh0ZXh0PXZhcikpID4gcXVhbnRpbGUoZXZhbChwYXJzZSh0ZXh0PXZhcikpLDAuMDEsIG5hLnJtPVRSVUUpKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKHg9ZXZhbChwYXJzZSh0ZXh0PXZhcikpKSkgKyANCiAgICBnZW9tX2RlbnNpdHkoKSArIHhsYWIodmFyKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KIyBEZW5zaXR5IHBsb3QgZm9yIDFzdCB0byA5OXRoIHBlcmNlbnRpbGUgb2YgYm90aCBjb2x1bW5zDQpwbHRfYmFyIDwtIGZ1bmN0aW9uKGRmLHZhcikgew0KICBkZiAlPiUNCiAgICBmaWx0ZXIoZXZhbChwYXJzZSh0ZXh0PXZhcikpIDwgcXVhbnRpbGUoZXZhbChwYXJzZSh0ZXh0PXZhcikpLDAuOTksIG5hLnJtPVRSVUUpLA0KICAgICAgICAgICBldmFsKHBhcnNlKHRleHQ9dmFyKSkgPiBxdWFudGlsZShldmFsKHBhcnNlKHRleHQ9dmFyKSksMC4wMSwgbmEucm09VFJVRSkpICU+JQ0KICAgIGdncGxvdChhZXMoeT1ldmFsKHBhcnNlKHRleHQ9dmFyKSksIHg9ZnF0cikpICsgDQogICAgZ2VvbV9iYXIoc3RhdCA9ICJzdW1tYXJ5IiwgZnVuLnkgPSAibWVhbiIpICsgeGxhYih2YXIpDQp9DQpgYGANCg0KYGBge3J9DQojIFNjYXR0ZXIgcGxvdCB3aXRoIGxhZyBmb3IgMXN0IHRvIDk5dGggcGVyY2VudGlsZSBvZiBkYXRhDQpwbHRfc2N0IDwtIGZ1bmN0aW9uKGRmLHZhcjEsIHZhcjIpIHsNCiAgZGYgJT4lDQogICAgZmlsdGVyKGV2YWwocGFyc2UodGV4dD12YXIxKSkgPCBxdWFudGlsZShldmFsKHBhcnNlKHRleHQ9dmFyMSkpLDAuOTksIG5hLnJtPVRSVUUpLA0KICAgICAgICAgICBldmFsKHBhcnNlKHRleHQ9dmFyMikpIDwgcXVhbnRpbGUoZXZhbChwYXJzZSh0ZXh0PXZhcjIpKSwwLjk5LCBuYS5ybT1UUlVFKSwNCiAgICAgICAgICAgZXZhbChwYXJzZSh0ZXh0PXZhcjEpKSA+IHF1YW50aWxlKGV2YWwocGFyc2UodGV4dD12YXIxKSksMC4wMSwgbmEucm09VFJVRSksDQogICAgICAgICAgIGV2YWwocGFyc2UodGV4dD12YXIyKSkgPiBxdWFudGlsZShldmFsKHBhcnNlKHRleHQ9dmFyMikpLDAuMDEsIG5hLnJtPVRSVUUpKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKHk9ZXZhbChwYXJzZSh0ZXh0PXZhcjEpKSwgeD1ldmFsKHBhcnNlKHRleHQ9dmFyMikpLCBjb2xvcj1mYWN0b3IoZnF0cikpKSArIA0KICAgIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsgeWxhYih2YXIxKSArIHhsYWIodmFyMikNCn0NCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NH0NCnBsdF9kaXN0KHRyYWluLCAicmV2dHEiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD00fQ0KcGx0X2Rpc3QodHJhaW4sICJyZXZ0cV9nciIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTR9DQpwbHRfZGlzdCh0cmFpbiwgInJldnRxX3lveSIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTR9DQpwbHRfZGlzdCh0cmFpbiwgInJldnRxX2QiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD00fQ0KcGx0X2Jhcih0cmFpbiwgInJldnRxIikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NH0NCnBsdF9iYXIodHJhaW4sICJyZXZ0cV9nciIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTR9DQpwbHRfYmFyKHRyYWluLCAicmV2dHFfeW95IikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NH0NCnBsdF9iYXIodHJhaW4sICJyZXZ0cV9kIikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NH0NCnBsdF9zY3QodHJhaW4sICJyZXZ0cSIsICJyZXZ0cV9sMSIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTR9DQpwbHRfc2N0KHRyYWluLCAicmV2dHFfZ3IiLCAicmV2dHFfZ3IxIikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NH0NCnBsdF9zY3QodHJhaW4sICJyZXZ0cV95b3kiLCAicmV2dHFfeW95MSIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTR9DQpwbHRfc2N0KHRyYWluLCAicmV2dHFfZCIsICJyZXZ0cV9kMSIpDQpgYGANCg0KYGBge3J9DQpjb3IodHJhaW5bLGMoInJldnRxIiwicmV2dHFfbDEiLCJyZXZ0cV9sMiIsInJldnRxX2wzIiwgInJldnRxX2w0IildLA0KICAgIHVzZT0iY29tcGxldGUub2JzIikNCmBgYA0KDQpgYGB7cn0NCmNvcih0cmFpblssYygicmV2dHFfZ3IiLCJyZXZ0cV9ncjEiLCJyZXZ0cV9ncjIiLCJyZXZ0cV9ncjMiLCAicmV2dHFfZ3I0IildLA0KICAgIHVzZT0iY29tcGxldGUub2JzIikNCmBgYA0KDQpgYGB7cn0NCmNvcih0cmFpblssYygicmV2dHFfeW95IiwicmV2dHFfeW95MSIsInJldnRxX3lveTIiLCJyZXZ0cV95b3kzIiwgInJldnRxX3lveTQiKV0sDQogICAgdXNlPSJjb21wbGV0ZS5vYnMiKQ0KYGBgDQoNCmBgYHtyfQ0KY29yKHRyYWluWyxjKCJyZXZ0cV9kIiwicmV2dHFfZDEiLCJyZXZ0cV9kMiIsInJldnRxX2QzIiwgInJldnRxX2Q0IildLA0KICAgIHVzZT0iY29tcGxldGUub2JzIikNCmBgYA0KDQpgYGB7cn0NCm1vZDEgPC0gbG0ocmV2dHEgfiByZXZ0cV9sMSwgZGF0YT10cmFpbikNCmBgYA0KDQpgYGB7cn0NCm1vZDIgPC0gbG0ocmV2dHEgfiByZXZ0cV9sMSArIHJldnRxX2w0LCBkYXRhPXRyYWluKQ0KYGBgDQoNCmBgYHtyfQ0KbW9kMyA8LSBsbShyZXZ0cSB+IHJldnRxX2wxICsgcmV2dHFfbDIgKyByZXZ0cV9sMyArIHJldnRxX2w0ICsgDQogICAgICAgICAgICAgcmV2dHFfbDUgKyByZXZ0cV9sNiArIHJldnRxX2w3ICsgcmV2dHFfbDgsIGRhdGE9dHJhaW4pDQpgYGANCg0KYGBge3J9DQptb2Q0IDwtIGxtKHJldnRxIH4gKHJldnRxX2wxICsgcmV2dHFfbDIgKyByZXZ0cV9sMyArIHJldnRxX2w0ICsNCiAgICAgICAgICAgICByZXZ0cV9sNSArIHJldnRxX2w2ICsgcmV2dHFfbDcgKyByZXZ0cV9sOCk6ZmFjdG9yKGZxdHIpLA0KICAgICAgICAgICBkYXRhPXRyYWluKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2QxKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2QyKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2QzKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2Q0KQ0KYGBgDQoNCmBgYHtyfQ0Kcm1zZSA8LSBmdW5jdGlvbih2MSwgdjIpIHsNCiAgc3FydChtZWFuKCh2MSAtIHYyKV4yLCBuYS5ybT1UKSkNCn0NCmBgYA0KDQpgYGB7cn0NCm1hZSA8LSBmdW5jdGlvbih2MSwgdjIpIHsNCiAgbWVhbihhYnModjEtdjIpLCBuYS5ybT1UKQ0KfQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCm1vZGVscyA8LSBsaXN0KG1vZDEsbW9kMixtb2QzLG1vZDQpDQptb2RlbF9uYW1lcyA8LSBjKCIxIHBlcmlvZCIsICIxIGFuZCA0IHBlcmlvZHMiLCAiOCBwZXJpb2RzIiwgIjggcGVyaW9kcyB3LyBxdWFydGVycyIpDQoNCmRmX3Rlc3QgPC0gZGF0YS5mcmFtZShhZGpfcl9zcT1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXN1bW1hcnkoeClbWyJhZGouci5zcXVhcmVkIl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0cmFpbiRyZXZ0cSwgcHJlZGljdCh4LHRyYWluKSkpLA0KICAgICAgICAgICAgICAgICAgICAgIG1hZV9pbj1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0cmFpbiRyZXZ0cSwgcHJlZGljdCh4LHRyYWluKSkpLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2Vfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0ZXN0JHJldnRxLCBwcmVkaWN0KHgsdGVzdCkpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHEsIHByZWRpY3QoeCx0ZXN0KSkpKQ0Kcm93bmFtZXMoZGZfdGVzdCkgPC0gbW9kZWxfbmFtZXMNCmh0bWxfZGYoZGZfdGVzdCkgICMgQ3VzdG9tIGZ1bmN0aW9uIHVzaW5nIGtuaXRyIGFuZCBrYWJsZUV4dHJhDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD00LjV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD1wcmVkaWN0KG1vZDEsdGVzdCksIGNvbG9yPWZhY3RvcihmcXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDEgcGVyaW9kIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTQuNX0NCnRlc3QgJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0cSx4PXByZWRpY3QobW9kNCx0ZXN0KSwgY29sb3I9ZmFjdG9yKGZxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIikgKyANCiAgeGxhYigiUHJlZGljdGlvbjogOCBwZXJpb2QgWCBxdWFydGVyIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQojIG1vZGVscw0KbW9kMWcgPC0gbG0ocmV2dHFfZ3IgfiByZXZ0cV9ncjEsIGRhdGE9dHJhaW4pDQptb2QyZyA8LSBsbShyZXZ0cV9nciB+IHJldnRxX2dyMSArIHJldnRxX2dyNCwgZGF0YT10cmFpbikNCm1vZDNnIDwtIGxtKHJldnRxX2dyIH4gcmV2dHFfZ3IxICsgcmV2dHFfZ3IyICsgcmV2dHFfZ3IzICsgcmV2dHFfZ3I0ICsgcmV2dHFfZ3I1ICsgcmV2dHFfZ3I2ICsgcmV2dHFfZ3I3ICsgcmV2dHFfZ3I4LCBkYXRhPXRyYWluKQ0KbW9kNGcgPC0gbG0ocmV2dHFfZ3IgfiAocmV2dHFfZ3IxICsgcmV2dHFfZ3IyICsgcmV2dHFfZ3IzICsgcmV2dHFfZ3I0ICsgcmV2dHFfZ3I1ICsgcmV2dHFfZ3I2ICsgcmV2dHFfZ3I3ICsgcmV2dHFfZ3I4KTpmYWN0b3IoZnF0ciksIGRhdGE9dHJhaW4pDQoNCm1vZGVscyA8LSBsaXN0KG1vZDFnLCBtb2QyZywgbW9kM2csIG1vZDRnKQ0KbW9kZWxfbmFtZXMgPC0gYygiMSBwZXJpb2QiLCAiMSBhbmQgNCBwZXJpb2RzIiwgIjggcGVyaW9kcyIsICI4IHBlcmlvZHMgdy8gcXVhcnRlcnMiKQ0KDQpkZl90ZXN0IDwtIGRhdGEuZnJhbWUoYWRqX3Jfc3E9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClzdW1tYXJ5KHgpW1siYWRqLnIuc3F1YXJlZCJdXSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9pbj1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXJtc2UodHJhaW4kcmV2dHEsICgxK3ByZWRpY3QoeCx0cmFpbikpKnRyYWluJHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxLCAoMStwcmVkaWN0KHgsdHJhaW4pKSp0cmFpbiRyZXZ0cV9sMSkpLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2Vfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0ZXN0JHJldnRxLCAoMStwcmVkaWN0KHgsdGVzdCkpKnRlc3QkcmV2dHFfbDEpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHEsICgxK3ByZWRpY3QoeCx0ZXN0KSkqdGVzdCRyZXZ0cV9sMSkpKQ0Kcm93bmFtZXMoZGZfdGVzdCkgPC0gbW9kZWxfbmFtZXMNCmh0bWxfZGYoZGZfdGVzdCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD0oMStwcmVkaWN0KG1vZDFnLHRlc3QpKSp0ZXN0JHJldnRxX2wxLCBjb2xvcj1mYWN0b3IoZnF0cikpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsgZ2VvbV9wb2ludCgpICsNCiAgeWxhYigiQWN0dWFsIHJldmVudWUiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiAxIHBlcmlvZCBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxLHg9KDErcHJlZGljdChtb2Q0Zyx0ZXN0KSkqdGVzdCRyZXZ0cV9sMSwgY29sb3I9ZmFjdG9yKGZxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIikgKyANCiAgeGxhYigiUHJlZGljdGlvbjogOCBwZXJpb2QgWCBxdWFydGVyIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQojIG1vZGVscw0KbW9kMXkgPC0gbG0ocmV2dHFfeW95IH4gcmV2dHFfeW95MSwgZGF0YT10cmFpbikNCm1vZDJ5IDwtIGxtKHJldnRxX3lveSB+IHJldnRxX3lveTEgKyByZXZ0cV95b3k0LCBkYXRhPXRyYWluKQ0KbW9kM3kgPC0gbG0ocmV2dHFfeW95IH4gcmV2dHFfeW95MSArIHJldnRxX3lveTIgKyByZXZ0cV95b3kzICsgcmV2dHFfeW95NCArIHJldnRxX3lveTUgKyByZXZ0cV95b3k2ICsgcmV2dHFfeW95NyArIHJldnRxX3lveTgsIGRhdGE9dHJhaW4pDQptb2Q0eSA8LSBsbShyZXZ0cV9nciB+IChyZXZ0cV95b3kxICsgcmV2dHFfeW95MiArIHJldnRxX3lveTMgKyByZXZ0cV95b3k0ICsgcmV2dHFfeW95NSArIHJldnRxX3lveTYgKyByZXZ0cV95b3k3ICsgcmV2dHFfeW95OCk6ZmFjdG9yKGZxdHIpLCBkYXRhPXRyYWluKQ0KDQptb2RlbHMgPC0gbGlzdChtb2QxeSwgbW9kMnksIG1vZDN5LCBtb2Q0eSkNCm1vZGVsX25hbWVzIDwtIGMoIjEgcGVyaW9kIiwgIjEgYW5kIDQgcGVyaW9kcyIsICI4IHBlcmlvZHMiLCAiOCBwZXJpb2RzIHcvIHF1YXJ0ZXJzIikNCg0KZGZfdGVzdCA8LSBkYXRhLmZyYW1lKGFkal9yX3NxPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpc3VtbWFyeSh4KVtbImFkai5yLnNxdWFyZWQiXV0pLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2VfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRyYWluJHJldnRxLCAoMStwcmVkaWN0KHgsdHJhaW4pKSp0cmFpbiRyZXZ0cV9sNCkpLA0KICAgICAgICAgICAgICAgICAgICAgIG1hZV9pbj1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0cmFpbiRyZXZ0cSwgKDErcHJlZGljdCh4LHRyYWluKSkqdHJhaW4kcmV2dHFfbDQpKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXJtc2UodGVzdCRyZXZ0cSwgKDErcHJlZGljdCh4LHRlc3QpKSp0ZXN0JHJldnRxX2w0KSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0ZXN0JHJldnRxLCAoMStwcmVkaWN0KHgsdGVzdCkpKnRlc3QkcmV2dHFfbDQpKSkNCnJvd25hbWVzKGRmX3Rlc3QpIDwtIG1vZGVsX25hbWVzDQpodG1sX2RmKGRmX3Rlc3QpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxLHg9KDErcHJlZGljdChtb2QxeSx0ZXN0KSkqdGVzdCRyZXZ0cV9sNCwgY29sb3I9ZmFjdG9yKGZxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIikgKyANCiAgeGxhYigiUHJlZGljdGlvbjogMSBwZXJpb2QgbW9kZWwiKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NX0NCnRlc3QgJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0cSx4PSgxK3ByZWRpY3QobW9kM3ksdGVzdCkpKnRlc3QkcmV2dHFfbDQsIGNvbG9yPWZhY3RvcihmcXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDggcGVyaW9kIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQojIG1vZGVscw0KbW9kMWQgPC0gbG0ocmV2dHFfZCB+IHJldnRxX2QxLCBkYXRhPXRyYWluKQ0KbW9kMmQgPC0gbG0ocmV2dHFfZCB+IHJldnRxX2QxICsgcmV2dHFfZDQsIGRhdGE9dHJhaW4pDQptb2QzZCA8LSBsbShyZXZ0cV9kIH4gcmV2dHFfZDEgKyByZXZ0cV9kMiArIHJldnRxX2QzICsgcmV2dHFfZDQgKyByZXZ0cV9kNSArIHJldnRxX2Q2ICsgcmV2dHFfZDcgKyByZXZ0cV9kOCwgZGF0YT10cmFpbikNCm1vZDRkIDwtIGxtKHJldnRxX2QgfiAocmV2dHFfZDEgKyByZXZ0cV9kMiArIHJldnRxX2QzICsgcmV2dHFfZDQgKyByZXZ0cV9kNSArIHJldnRxX2Q2ICsgcmV2dHFfZDcgKyByZXZ0cV9kOCk6ZmFjdG9yKGZxdHIpLCBkYXRhPXRyYWluKQ0KDQptb2RlbHMgPC0gbGlzdChtb2QxZCwgbW9kMmQsIG1vZDNkLCBtb2Q0ZCkNCm1vZGVsX25hbWVzIDwtIGMoIjEgcGVyaW9kIiwgIjEgYW5kIDQgcGVyaW9kcyIsICI4IHBlcmlvZHMiLCAiOCBwZXJpb2RzIHcvIHF1YXJ0ZXJzIikNCg0KZGZfdGVzdCA8LSBkYXRhLmZyYW1lKGFkal9yX3NxPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpc3VtbWFyeSh4KVtbImFkai5yLnNxdWFyZWQiXV0pLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2VfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRyYWluJHJldnRxLCBwcmVkaWN0KHgsdHJhaW4pK3RyYWluJHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxLCBwcmVkaWN0KHgsdHJhaW4pK3RyYWluJHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRlc3QkcmV2dHEsIHByZWRpY3QoeCx0ZXN0KSt0ZXN0JHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0ZXN0JHJldnRxLCBwcmVkaWN0KHgsdGVzdCkrdGVzdCRyZXZ0cV9sMSkpKQ0Kcm93bmFtZXMoZGZfdGVzdCkgPC0gbW9kZWxfbmFtZXMNCmh0bWxfZGYoZGZfdGVzdCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD1wcmVkaWN0KG1vZDFkLHRlc3QpK3Rlc3QkcmV2dHFfbDEsIGNvbG9yPWZhY3RvcihmcXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDEgcGVyaW9kIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD1wcmVkaWN0KG1vZDRkLHRlc3QpK3Rlc3QkcmV2dHFfbDEsIGNvbG9yPWZhY3RvcihmcXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDggcGVyaW9kIFggcXVhcnRlciBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KIyBtb2RlbHMNCm1vZDFnIDwtIGxtKHJldnRxX2dyIH4gcmV2dHFfZ3IxLCBkYXRhPXRyYWluKQ0KbW9kMmcgPC0gbG0ocmV2dHFfZ3IgfiByZXZ0cV9ncjEgKyByZXZ0cV9ncjQsIGRhdGE9dHJhaW4pDQptb2QzZyA8LSBsbShyZXZ0cV9nciB+IHJldnRxX2dyMSArIHJldnRxX2dyMiArIHJldnRxX2dyMyArIHJldnRxX2dyNCArIHJldnRxX2dyNSArIHJldnRxX2dyNiArIHJldnRxX2dyNyArIHJldnRxX2dyOCwgZGF0YT10cmFpbikNCm1vZDRnIDwtIGxtKHJldnRxX2dyIH4gKHJldnRxX2dyMSArIHJldnRxX2dyMiArIHJldnRxX2dyMyArIHJldnRxX2dyNCArIHJldnRxX2dyNSArIHJldnRxX2dyNiArIHJldnRxX2dyNyArIHJldnRxX2dyOCk6ZmFjdG9yKGZxdHIpLCBkYXRhPXRyYWluKQ0KDQptb2RlbHMgPC0gbGlzdChtb2QxZywgbW9kMmcsIG1vZDNnLCBtb2Q0ZykNCm1vZGVsX25hbWVzIDwtIGMoIjEgcGVyaW9kIiwgIjEgYW5kIDQgcGVyaW9kcyIsICI4IHBlcmlvZHMiLCAiOCBwZXJpb2RzIHcvIHF1YXJ0ZXJzIikNCg0KZGZfdGVzdCA8LSBkYXRhLmZyYW1lKGFkal9yX3NxPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpc3VtbWFyeSh4KVtbImFkai5yLnNxdWFyZWQiXV0pLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2VfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRyYWluJHJldnRxX2dyLCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxX2dyLCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRlc3QkcmV2dHFfZ3IsIHByZWRpY3QoeCx0ZXN0KSkpLA0KICAgICAgICAgICAgICAgICAgICAgIG1hZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeCltYWUodGVzdCRyZXZ0cV9nciwgcHJlZGljdCh4LHRlc3QpKSkpDQpyb3duYW1lcyhkZl90ZXN0KSA8LSBtb2RlbF9uYW1lcw0KaHRtbF9kZihkZl90ZXN0KQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NX0NCnRlc3QgJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0cV9ncix4PXByZWRpY3QobW9kMWcsdGVzdCksIGNvbG9yPWZhY3RvcihmcXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSBncm93dGgiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiAxIHBlcmlvZCBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxX2dyLHg9cHJlZGljdChtb2Q0Zyx0ZXN0KSwgY29sb3I9ZmFjdG9yKGZxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIGdyb3d0aCIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDggcGVyaW9kIFggcXVhcnRlciBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KIyBtb2RlbHMNCm1vZDF5IDwtIGxtKHJldnRxX3lveSB+IHJldnRxX3lveTEsIGRhdGE9dHJhaW4pDQptb2QyeSA8LSBsbShyZXZ0cV95b3kgfiByZXZ0cV95b3kxICsgcmV2dHFfeW95NCwgZGF0YT10cmFpbikNCm1vZDN5IDwtIGxtKHJldnRxX3lveSB+IHJldnRxX3lveTEgKyByZXZ0cV95b3kyICsgcmV2dHFfeW95MyArIHJldnRxX3lveTQgKyByZXZ0cV95b3k1ICsgcmV2dHFfeW95NiArIHJldnRxX3lveTcgKyByZXZ0cV95b3k4LCBkYXRhPXRyYWluKQ0KbW9kNHkgPC0gbG0ocmV2dHFfZ3IgfiAocmV2dHFfeW95MSArIHJldnRxX3lveTIgKyByZXZ0cV95b3kzICsgcmV2dHFfeW95NCArIHJldnRxX3lveTUgKyByZXZ0cV95b3k2ICsgcmV2dHFfeW95NyArIHJldnRxX3lveTgpOmZhY3RvcihmcXRyKSwgZGF0YT10cmFpbikNCg0KbW9kZWxzIDwtIGxpc3QobW9kMXksIG1vZDJ5LCBtb2QzeSwgbW9kNHkpDQptb2RlbF9uYW1lcyA8LSBjKCIxIHBlcmlvZCIsICIxIGFuZCA0IHBlcmlvZHMiLCAiOCBwZXJpb2RzIiwgIjggcGVyaW9kcyB3LyBxdWFydGVycyIpDQoNCmRmX3Rlc3QgPC0gZGF0YS5mcmFtZShhZGpfcl9zcT1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXN1bW1hcnkoeClbWyJhZGouci5zcXVhcmVkIl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0cmFpbiRyZXZ0cV95b3ksIHByZWRpY3QoeCx0cmFpbikpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeCltYWUodHJhaW4kcmV2dHFfeW95LCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRlc3QkcmV2dHFfeW95LCBwcmVkaWN0KHgsdGVzdCkpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHFfeW95LCBwcmVkaWN0KHgsdGVzdCkpKSkNCnJvd25hbWVzKGRmX3Rlc3QpIDwtIG1vZGVsX25hbWVzDQpodG1sX2RmKGRmX3Rlc3QpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxX3lveSx4PXByZWRpY3QobW9kMXksdGVzdCksIGNvbG9yPWZhY3RvcihmcXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgeWVhciBvdmVyIHllYXIgcmV2ZW51ZSBncm93dGgiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiAxIHBlcmlvZCBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxX3lveSx4PXByZWRpY3QobW9kM3ksdGVzdCksIGNvbG9yPWZhY3RvcihmcXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgeWVhciBvdmVyIHllYXIgcmV2ZW51ZSBncm93dGgiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiA4IHBlcmlvZCBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KIyBtb2RlbHMNCm1vZDFkIDwtIGxtKHJldnRxX2QgfiByZXZ0cV9kMSwgZGF0YT10cmFpbikNCm1vZDJkIDwtIGxtKHJldnRxX2QgfiByZXZ0cV9kMSArIHJldnRxX2Q0LCBkYXRhPXRyYWluKQ0KbW9kM2QgPC0gbG0ocmV2dHFfZCB+IHJldnRxX2QxICsgcmV2dHFfZDIgKyByZXZ0cV9kMyArIHJldnRxX2Q0ICsgcmV2dHFfZDUgKyByZXZ0cV9kNiArIHJldnRxX2Q3ICsgcmV2dHFfZDgsIGRhdGE9dHJhaW4pDQptb2Q0ZCA8LSBsbShyZXZ0cV9kIH4gKHJldnRxX2QxICsgcmV2dHFfZDIgKyByZXZ0cV9kMyArIHJldnRxX2Q0ICsgcmV2dHFfZDUgKyByZXZ0cV9kNiArIHJldnRxX2Q3ICsgcmV2dHFfZDgpOmZhY3RvcihmcXRyKSwgZGF0YT10cmFpbikNCg0KbW9kZWxzIDwtIGxpc3QobW9kMWQsIG1vZDJkLCBtb2QzZCwgbW9kNGQpDQptb2RlbF9uYW1lcyA8LSBjKCIxIHBlcmlvZCIsICIxIGFuZCA0IHBlcmlvZHMiLCAiOCBwZXJpb2RzIiwgIjggcGVyaW9kcyB3LyBxdWFydGVycyIpDQoNCmRmX3Rlc3QgPC0gZGF0YS5mcmFtZShhZGpfcl9zcT1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXN1bW1hcnkoeClbWyJhZGouci5zcXVhcmVkIl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0cmFpbiRyZXZ0cV9kLCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxX2QsIHByZWRpY3QoeCx0cmFpbikpKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXJtc2UodGVzdCRyZXZ0cV9kLCBwcmVkaWN0KHgsdGVzdCkpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHFfZCwgcHJlZGljdCh4LHRlc3QpKSkpDQpyb3duYW1lcyhkZl90ZXN0KSA8LSBtb2RlbF9uYW1lcw0KaHRtbF9kZihkZl90ZXN0KQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NX0NCnRlc3QgJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0cV9kLHg9cHJlZGljdChtb2QxZCx0ZXN0KSwgY29sb3I9ZmFjdG9yKGZxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIGZpcnN0IGRpZmZlcmVuY2UiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiAxIHBlcmlvZCBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxX2QseD1wcmVkaWN0KG1vZDRkLHRlc3QpLCBjb2xvcj1mYWN0b3IoZnF0cikpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsgZ2VvbV9wb2ludCgpICsNCiAgeWxhYigiQWN0dWFsIHJldmVudWUgZmlyc3QgZGlmZmVyZW5jZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDggcGVyaW9kIFggcXVhcnRlciBtb2RlbCIpDQpgYGANCg0K