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)
There were 19 warnings (use warnings() to see them)
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
-- Column specification ---------------------------------------------------------------------------------------------------------------------------------------
cols(
quarter = col_character(),
level_1 = col_character(),
level_2 = col_character(),
level_3 = col_character(),
value = col_character()
)
# 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
ungroup()
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
revenue_mean = mean(df_SG_macro$revt, na.rm=T)
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 * r_sd / revenue_mean,1)
rev_min <- round((r_min * rev / revenue_mean)*100,1)
rev_max <- round((r_max * rev / revenue_mean)*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 <- ymd(df$datadate) # From lubridate
df$qtr <- quarter(df$date) # From lubridate
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")])
# Make a copy of our data frame to compare later
df_purrr <- df
# Approach #1: Advanced programming using quosures...
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(ungroup(mutate(group_by(df, gvkey), !!!quosures)))
}
# Approach #2: Mixing purrr with dplyr across()
library(purrr)
multi_lag_purrr <- function(df, lags, var, postfix="") {
new_columns <-
map_dfc(lags,
function(x) df %>%
group_by(gvkey) %>%
transmute(across(all_of(var),
.fns = list(~ lag(., x)),
.names = paste0('{col}', postfix, x) ) ) %>%
ungroup() %>%
select(-gvkey))
cbind(df, new_columns)
}
df <- multi_lag(df, 1:8, revtq, "_l") # Generate lags "revtq_l#"
df <- multi_lag(df, 1:8, revtq_gr) # Generate changes "revtq_gr#"
df <- multi_lag(df, 1:8, revtq_yoy) # Generate year-over-year changes "revtq_yoy#"
df <- multi_lag(df, 1:8, revtq_d) # Generate first differences "revtq_d#"
df_purrr <- multi_lag_purrr(df_purrr, 1:8, 'revtq', "_l") # Generate lags "revtq_l#"
df_purrr <- multi_lag_purrr(df_purrr, 1:8, 'revtq_gr') # Generate changes "revtq_gr#"
df_purrr <- multi_lag_purrr(df_purrr, 1:8, 'revtq_yoy') # Generate year-over-year changes "revtq_yoy#"
df_purrr <- multi_lag_purrr(df_purrr, 1:8, 'revtq_d') # Generate first differences "revtq_d#"
all(df==df_purrr, na.rm=T)
[1] TRUE
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(across(where(is.numeric), ~replace(., !is.finite(.), NA)))
# 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","qtr")])
revtq revtq_gr revtq_yoy revtq_d qtr
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.:2.000
Median : 273.95 Median : 0.0505 Median : 0.0740 Median : 4.30 Median :3.000
Mean : 2439.38 Mean : 0.0650 Mean : 0.1273 Mean : 22.66 Mean :2.503
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=qtr)) +
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(qtr))) +
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")
No summary function supplied, defaulting to `mean_se()`

plt_bar(train, "revtq_gr")
No summary function supplied, defaulting to `mean_se()`

plt_bar(train, "revtq_yoy")
No summary function supplied, defaulting to `mean_se()`

plt_bar(train, "revtq_d")
No summary function supplied, defaulting to `mean_se()`

plt_sct(train, "revtq", "revtq_l1")
`geom_smooth()` using formula 'y ~ x'

plt_sct(train, "revtq_gr", "revtq_gr1")
`geom_smooth()` using formula 'y ~ x'

plt_sct(train, "revtq_yoy", "revtq_yoy1")
`geom_smooth()` using formula 'y ~ x'

plt_sct(train, "revtq_d", "revtq_d1")
`geom_smooth()` using formula 'y ~ x'

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(qtr), 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(qtr), data = train)
Residuals:
Min 1Q Median 3Q Max
-5316.5 -12.2 0.9 15.7 5283.2
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.45240 4.32484 -0.105 0.916692
revtq_l1:factor(qtr)1 0.91094 0.02610 34.896 < 2e-16 ***
revtq_l1:factor(qtr)2 0.67361 0.02376 28.355 < 2e-16 ***
revtq_l1:factor(qtr)3 0.97588 0.02747 35.525 < 2e-16 ***
revtq_l1:factor(qtr)4 0.65106 0.02216 29.377 < 2e-16 ***
revtq_l2:factor(qtr)1 0.05733 0.02872 1.996 0.045997 *
revtq_l2:factor(qtr)2 0.14708 0.03410 4.313 1.64e-05 ***
revtq_l2:factor(qtr)3 0.02910 0.02976 0.978 0.328253
revtq_l2:factor(qtr)4 0.36807 0.03468 10.614 < 2e-16 ***
revtq_l3:factor(qtr)1 -0.09063 0.03717 -2.438 0.014788 *
revtq_l3:factor(qtr)2 0.05182 0.02865 1.809 0.070567 .
revtq_l3:factor(qtr)3 -0.19920 0.03424 -5.818 6.23e-09 ***
revtq_l3:factor(qtr)4 -0.06628 0.02623 -2.527 0.011534 *
revtq_l4:factor(qtr)1 0.92463 0.02297 40.246 < 2e-16 ***
revtq_l4:factor(qtr)2 0.45135 0.03497 12.906 < 2e-16 ***
revtq_l4:factor(qtr)3 0.86260 0.02592 33.283 < 2e-16 ***
revtq_l4:factor(qtr)4 0.70500 0.02815 25.044 < 2e-16 ***
revtq_l5:factor(qtr)1 -0.64846 0.03135 -20.684 < 2e-16 ***
revtq_l5:factor(qtr)2 -0.54217 0.02742 -19.769 < 2e-16 ***
revtq_l5:factor(qtr)3 -0.60937 0.03426 -17.788 < 2e-16 ***
revtq_l5:factor(qtr)4 -0.60983 0.02552 -23.895 < 2e-16 ***
revtq_l6:factor(qtr)1 0.03087 0.03054 1.011 0.312044
revtq_l6:factor(qtr)2 0.07480 0.03428 2.182 0.029121 *
revtq_l6:factor(qtr)3 -0.05330 0.03071 -1.736 0.082618 .
revtq_l6:factor(qtr)4 -0.13895 0.03654 -3.803 0.000144 ***
revtq_l7:factor(qtr)1 -0.33575 0.03845 -8.731 < 2e-16 ***
revtq_l7:factor(qtr)2 0.08286 0.03055 2.712 0.006696 **
revtq_l7:factor(qtr)3 -0.07259 0.03403 -2.133 0.032969 *
revtq_l7:factor(qtr)4 0.05999 0.02721 2.205 0.027508 *
revtq_l8:factor(qtr)1 0.13800 0.02437 5.664 1.54e-08 ***
revtq_l8:factor(qtr)2 0.04951 0.02802 1.767 0.077331 .
revtq_l8:factor(qtr)3 0.09017 0.02624 3.436 0.000593 ***
revtq_l8:factor(qtr)4 0.04742 0.01974 2.402 0.016313 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 324.5 on 6642 degrees of freedom
(1665 observations deleted due to missingness)
Multiple R-squared: 0.9987, Adjusted R-squared: 0.9987
F-statistic: 1.65e+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.9987376 |
323.6768 |
94.07378 |
633.8951 |
332.0945 |
test %>%
ggplot(aes(y=revtq,x=predict(mod1,test), color=factor(qtr))) +
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(qtr))) +
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(qtr), 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.4833 |
3374.728 |
1397.6541 |
1 and 4 periods |
0.4398456 |
530.6444 |
154.1509 |
1447.035 |
679.3536 |
8 periods |
0.6761666 |
456.2551 |
123.3407 |
1254.201 |
584.9709 |
8 periods w/ quarters |
0.7547897 |
423.7594 |
113.6537 |
1169.282 |
537.2325 |
test %>%
ggplot(aes(y=revtq,x=(1+predict(mod1g,test))*test$revtq_l1, color=factor(qtr))) +
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(qtr))) +
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(qtr), 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.1040702 |
679.9093 |
187.4486 |
1330.7890 |
658.4296 |
test %>%
ggplot(aes(y=revtq,x=(1+predict(mod1y,test))*test$revtq_l4, color=factor(qtr))) +
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(qtr))) +
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(qtr), 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.9312580 |
312.2140 |
88.24559 |
661.4063 |
331.0617 |
test %>%
ggplot(aes(y=revtq,x=predict(mod1d,test)+test$revtq_l1, color=factor(qtr))) +
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(qtr))) +
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(qtr), 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.7547897 |
0.1530278 |
0.0816612 |
0.1433094 |
0.0745658 |
test %>%
ggplot(aes(y=revtq_gr,x=predict(mod1g,test), color=factor(qtr))) +
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(qtr))) +
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(qtr), 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.1040702 |
0.2986735 |
0.1380062 |
0.1960325 |
0.1020124 |
test %>%
ggplot(aes(y=revtq_yoy,x=predict(mod1y,test), color=factor(qtr))) +
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(qtr))) +
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(qtr), 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.9312580 |
312.2140 |
88.24559 |
661.4063 |
331.0617 |
test %>%
ggplot(aes(y=revtq_d,x=predict(mod1d,test), color=factor(qtr))) +
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(qtr))) +
geom_abline(slope=1) + geom_point() +
ylab("Actual revenue first difference") +
xlab("Prediction: 8 period X quarter model")

LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gMyINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCiANCk5vdGUgdGhhdCB0aGUgZGlyZWN0b3JpZXMgdXNlZCB0byBzdG9yZSBkYXRhIGFyZSBsaWtlbHkgZGlmZmVyZW50IG9uIHlvdXIgY29tcHV0ZXIsIGFuZCBzdWNoIHJlZmVyZW5jZXMgd2lsbCBuZWVkIHRvIGJlIGNoYW5nZWQgYmVmb3JlIHVzaW5nIGFueSBzdWNoIGNvZGUuDQoNCmBgYHtyIGhlbHBlcnMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9Rn0NCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpodG1sX2RmIDwtIGZ1bmN0aW9uKHRleHQsIGNvbHM9TlVMTCwgY29sMT1GQUxTRSwgZnVsbD1GKSB7DQogIGlmKCFsZW5ndGgoY29scykpIHsNCiAgICBjb2xzPWNvbG5hbWVzKHRleHQpDQogIH0NCiAgaWYoIWNvbDEpIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpDQogIH0gZWxzZSB7DQogICAga2FibGUodGV4dCwiaHRtbCIsIGNvbC5uYW1lcyA9IGNvbHMsIGFsaWduID0gYygibCIscmVwKCdjJyxsZW5ndGgoY29scyktMSkpKSAlPiUNCiAgICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwiaG92ZXIiKSwgZnVsbF93aWR0aD1mdWxsKSAlPiUNCiAgICAgIGNvbHVtbl9zcGVjKDEsYm9sZD1UKQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmRmIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fMy0xLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQp3bXQgPC0gZmlsdGVyKGRmLCB0aWMgPT0gIldNVCIpDQoNCiMgbG9hZCBpbiByZWxldmFudCBkYXRhIGZyb20gU2Vzc2lvbiAyDQpsb2FkKCIuLi8uLi9EYXRhL1Nlc3Npb25fMl9leHBvcnQuUkRhdGEiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCmV4cGVjdGF0aW9ucyA8LSByZWFkX2NzdigiLi4vLi4vRGF0YS9nZW5lcmFsLWJ1c2luZXNzLWV4cGVjdGF0aW9ucy1ieS1kZXRhaWxlZC1zZXJ2aWNlcy1pbmR1c3RyeS1xdWFydGVybHkuY3N2IikgJT4lDQogIG11dGF0ZSh5ZWFyID0gYXMubnVtZXJpYyhzdWJzdHIocXVhcnRlciwgMSwgNCkpKSAlPiUgICAgIyBzcGxpdCBvdXQgeWVhcg0KICBtdXRhdGUocXVhcnRlciA9IGFzLm51bWVyaWMoc3Vic3RyKHF1YXJ0ZXIsIDcsIDcpKSkgJT4lICMgc3BsaXQgb3V0IHF1YXJ0ZXINCiAgbXV0YXRlKHZhbHVlID0gYXMubnVtZXJpYyh2YWx1ZSkpICAgICAgICAgICAgICAgICAgICAgICAjIEVuc3VlIHZhbHVlIGlzIG51bWVyaWMNCmBgYA0KDQpgYGB7cn0NCiMgZXh0cmFjdCBvdXQgUTEsIGZpbmFuY2Ugb25seQ0KZXhwZWN0YXRpb25zX2F2ZyA8LSBleHBlY3RhdGlvbnMgJT4lDQogIGZpbHRlcihxdWFydGVyID09IDEsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRoZSBmaXJzdCBxdWFydGVyDQogICAgICAgICBsZXZlbF8yID09ICJGaW5hbmNpYWwgJiBJbnN1cmFuY2UiKSAlPiUgICAgICMgS2VlcCBvbmx5IGZpbmFuY2lhbCByZXNwb25zZXMNCiAgZ3JvdXBfYnkoeWVhcikgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBHcm91cCBkYXRhIGJ5IHllYXINCiAgbXV0YXRlKGZpbl9zZW50aW1lbnQ9bWVhbih2YWx1ZSwgbmEucm09VFJVRSkpICU+JSAgIyBDYWxjdWxhdGUgYXZlcmFnZQ0KICBzbGljZSgxKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFRha2Ugb25seSAxIHJvdyBwZXIgZ3JvdXANCiAgdW5ncm91cCgpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KERUKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZ9DQpleHBlY3RhdGlvbnMgJT4lDQogIGFycmFuZ2UobGV2ZWxfMiwgbGV2ZWxfMywgZGVzYyh5ZWFyKSkgJT4lICAjIHNvcnQgdGhlIGRhdGENCiAgc2VsZWN0KHllYXIsIHF1YXJ0ZXIsIGxldmVsXzIsIGxldmVsXzMsIHZhbHVlKSAlPiUgICMga2VlcCBvbmx5IHRoZXNlIHZhcmlhYmxlcw0KICBkYXRhdGFibGUob3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDUpLCByb3duYW1lcz1GQUxTRSkgICMgZGlzcGxheSB1c2luZyBEVA0KYGBgDQoNCmBgYHtyfQ0KIyBzdWJzZXQgb3V0IG91ciBTaW5nYXBvcmVhbiBkYXRhLCBzaW5jZSBvdXIgbWFjcm8gZGF0YSBpcyBTaW5nYXBvcmUtc3BlY2lmaWMNCmRmX1NHIDwtIGRmX2NsZWFuICU+JSBmaWx0ZXIoZmljID09ICJTR1AiKQ0KDQojIENyZWF0ZSB5ZWFyIGluIGRmX1NHIChkYXRlIGlzIGdpdmVuIGJ5IGRhdGFkYXRlIGFzIFlZWVlNTUREKQ0KZGZfU0ckeWVhciA9IHJvdW5kKGRmX1NHJGRhdGFkYXRlIC8gMTAwMDAsIGRpZ2l0cz0wKQ0KDQojIENvbWJpbmUgZGF0YXNldHMNCiMgTm90aWNlIGhvdyBpdCBhdXRvbWF0aWNhbGx5IGZpZ3VyZXMgb3V0IHRvIGpvaW4gYnkgInllYXIiDQpkZl9TR19tYWNybyA8LSBsZWZ0X2pvaW4oZGZfU0csIGV4cGVjdGF0aW9uc19hdmdbLGMoInllYXIiLCJmaW5fc2VudGltZW50IildKQ0KYGBgDQoNCmBgYHtyfQ0KbWFjcm8xIDwtIGxtKHJldnRfbGVhZCB+IHJldnQgKyBhY3QgKyBjaGUgKyBsY3QgKyBkcCArIGViaXQgKyBmaW5fc2VudGltZW50LA0KICAgICAgICAgICAgIGRhdGE9ZGZfU0dfbWFjcm8pDQpsaWJyYXJ5KGJyb29tKQ0KdGlkeShtYWNybzEpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD00fQ0KZGZfU0dfbWFjcm8gJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0X2xlYWQsDQogICAgICAgICAgICAgeD1maW5fc2VudGltZW50KSkgKyANCiAgZ2VvbV9wb2ludCgpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD00fQ0KZGZfU0dfbWFjcm8gJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0X2xlYWQsDQogICAgeD1zY2FsZShmaW5fc2VudGltZW50KSAqIHJldnQpKSArIA0KICBnZW9tX3BvaW50KCkNCmBgYA0KDQpgYGB7cn0NCiMgU2NhbGUgY3JlYXRlcyB6LXNjb3JlcywgYnV0IHJldHVybnMgYSBtYXRyaXggYnkgZGVmYXVsdC4gIFssMV0gZ2l2ZXMgYSB2ZWN0b3INCmRmX1NHX21hY3JvJGZpbl9zZW50X3NjYWxlZCA8LSBzY2FsZShkZl9TR19tYWNybyRmaW5fc2VudGltZW50KVssMV0NCm1hY3JvMyA8LQ0KICBsbShyZXZ0X2xlYWQgfiByZXZ0ICsgYWN0ICsgY2hlICsgbGN0ICsgZHAgKyBlYml0ICsgZmluX3NlbnRfc2NhbGVkOnJldnQsDQogICAgIGRhdGE9ZGZfU0dfbWFjcm8pDQp0aWR5KG1hY3JvMykNCmdsYW5jZShtYWNybzMpDQpgYGANCg0KYGBge3J9DQpiYXNlbGluZSA8LQ0KICBsbShyZXZ0X2xlYWQgfiByZXZ0ICsgYWN0ICsgY2hlICsgbGN0ICsgZHAgKyBlYml0LA0KICAgICBkYXRhPWRmX1NHX21hY3JvWyFpcy5uYShkZl9TR19tYWNybyRmaW5fc2VudGltZW50KSxdKQ0KZ2xhbmNlKGJhc2VsaW5lKQ0KZ2xhbmNlKG1hY3JvMykNCmBgYA0KDQpgYGB7cn0NCmFub3ZhKGJhc2VsaW5lLCBtYWNybzMsIHRlc3Q9IkNoaXNxIikNCmBgYA0KDQpgYGB7cn0NCnJldmVudWVfbWVhbiA9IG1lYW4oZGZfU0dfbWFjcm8kcmV2dCwgbmEucm09VCkNCnJfc2QgPC0gcm91bmQoc2QoZGZfU0dfbWFjcm8kZmluX3NlbnRpbWVudCwgbmEucm09VCksMSkNCnJfbWluIDwtIG1pbihkZl9TR19tYWNybyRmaW5fc2VudGltZW50LCBuYS5ybT1UKQ0Kcl9tYXggPC0gbWF4KGRmX1NHX21hY3JvJGZpbl9zZW50aW1lbnQsIG5hLnJtPVQpDQpyZXYgPC0gbWFjcm8zJGNvZWZmaWNpZW50c1tbInJldnQ6ZmluX3NlbnRfc2NhbGVkIl1dDQpyX3JldiA9IHJvdW5kKDEwMCAqcmV2ICogcl9zZCAvIHJldmVudWVfbWVhbiwxKQ0KcmV2X21pbiA8LSByb3VuZCgocl9taW4gKiByZXYgLyByZXZlbnVlX21lYW4pKjEwMCwxKQ0KcmV2X21heCA8LSByb3VuZCgocl9tYXggKiByZXYgLyByZXZlbnVlX21lYW4pKjEwMCwxKQ0KYGBgDQoNCmBgYHtyfQ0KcF91b2wgPC0gcHJlZGljdChmb3JlY2FzdDIsIHVvbFt1b2wkZnllYXI9PTIwMTcsXSkNCnBfYmFzZSA8LSBwcmVkaWN0KGJhc2VsaW5lLA0KICBkZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyPT0yMDE3LF0pDQpwX21hY3JvIDwtIHByZWRpY3QobWFjcm8zLA0KICBkZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyPT0yMDE3LF0pDQpwX3dvcmxkIDwtIHByZWRpY3QoZm9yZWNhc3Q0LA0KICBkZl9jbGVhbltkZl9jbGVhbiRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX2NsZWFuJGZ5ZWFyPT0yMDE3LF0pDQpwcmVkcyA8LSBjKHBfdW9sLCBwX2Jhc2UsIHBfbWFjcm8sIHBfd29ybGQpDQpuYW1lcyhwcmVkcykgPC0gYygiVU9MIDIwMTggVU9MIiwgIlVPTCAyMDE4IEJhc2UiLCAiVU9MIDIwMTggTWFjcm8iLA0KICAgICAgICAgICAgICAgICAgIlVPTCAyMDE4IFdvcmxkIikNCnByZWRzDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9Niwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpsaWJyYXJ5KHBsb3RseSkNCmRmX1NHX21hY3JvJHByZWRfYmFzZSA8LSBwcmVkaWN0KGJhc2VsaW5lLCBkZl9TR19tYWNybykNCmRmX1NHX21hY3JvJHByZWRfbWFjcm8gPC0gcHJlZGljdChtYWNybzMsIGRmX1NHX21hY3JvKQ0KZGZfY2xlYW4kcHJlZF93b3JsZCA8LSBwcmVkaWN0KGZvcmVjYXN0NCwgZGZfY2xlYW4pDQp1b2wkcHJlZF91b2wgPC0gcHJlZGljdChmb3JlY2FzdDIsIHVvbCkNCmRmX3ByZWRzIDwtIGRhdGEuZnJhbWUocHJlZHM9cHJlZHMsIGZ5ZWFyPWMoMjAxOCwyMDE4LDIwMTgsMjAxOCksIG1vZGVsPWMoIlVPTCBvbmx5IiwgIkJhc2UiLCAiTWFjcm8iLCAiV29ybGQiKSkNCnBsb3QgPC0gZ2dwbG90KCkgKyANCiAgZ2VvbV9wb2ludChkYXRhPWRmX1NHX21hY3JvW2RmX1NHX21hY3JvJGlzaW49PSJTRzFTODMwMDIzNDkiICYgZGZfU0dfbWFjcm8kZnllYXIgPCAyMDE3LF0sIGFlcyh5PXJldnRfbGVhZCx4PWZ5ZWFyLCBjb2xvcj0iQWN0dWFsIikpICsNCiAgZ2VvbV9saW5lKGRhdGE9ZGZfU0dfbWFjcm9bZGZfU0dfbWFjcm8kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9TR19tYWNybyRmeWVhciA8IDIwMTcsXSwgYWVzKHk9cmV2dF9sZWFkLHg9ZnllYXIsIGNvbG9yPSJBY3R1YWwiKSkgKyANCiAgZ2VvbV9wb2ludChkYXRhPXVvbFt1b2wkZnllYXIgPCAyMDE3LF0sIGFlcyh5PXByZWRfdW9sLHg9ZnllYXIsIGNvbG9yPSJVT0wgb25seSIpKSArDQogIGdlb21fbGluZShkYXRhPXVvbFt1b2wkZnllYXIgPCAyMDE3LF0sIGFlcyh5PXByZWRfdW9sLHg9ZnllYXIsIGNvbG9yPSJVT0wgb25seSIpKSArDQogIGdlb21fcG9pbnQoZGF0YT1kZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX2Jhc2UseD1meWVhciwgY29sb3I9IkJhc2UiKSkgKw0KICBnZW9tX2xpbmUoZGF0YT1kZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX2Jhc2UseD1meWVhciwgY29sb3I9IkJhc2UiKSkgKw0KICBnZW9tX3BvaW50KGRhdGE9ZGZfU0dfbWFjcm9bZGZfU0dfbWFjcm8kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9TR19tYWNybyRmeWVhciA8IDIwMTcsXSwgYWVzKHk9cHJlZF9tYWNybyx4PWZ5ZWFyLCBjb2xvcj0iTWFjcm8iKSkgKw0KICBnZW9tX2xpbmUoZGF0YT1kZl9TR19tYWNyb1tkZl9TR19tYWNybyRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX1NHX21hY3JvJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX21hY3JvLHg9ZnllYXIsIGNvbG9yPSJNYWNybyIpKSArIA0KICBnZW9tX3BvaW50KGRhdGE9ZGZfY2xlYW5bZGZfY2xlYW4kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9jbGVhbiRmeWVhciA8IDIwMTcsXSwgYWVzKHk9cHJlZF93b3JsZCx4PWZ5ZWFyLCBjb2xvcj0iV29ybGQiKSkgKw0KICBnZW9tX2xpbmUoZGF0YT1kZl9jbGVhbltkZl9jbGVhbiRpc2luPT0iU0cxUzgzMDAyMzQ5IiAmIGRmX2NsZWFuJGZ5ZWFyIDwgMjAxNyxdLCBhZXMoeT1wcmVkX3dvcmxkLHg9ZnllYXIsIGNvbG9yPSJXb3JsZCIpKSArIA0KICBnZW9tX3BvaW50KGRhdGE9ZGZfcHJlZHMsIGFlcyh5PXByZWRzLCB4PWZ5ZWFyLCBjb2xvcj1tb2RlbCksIHNpemU9MS41LCBzaGFwZT0xOCkNCmdncGxvdGx5KHBsb3QpDQpgYGANCg0KYGBge3J9DQphY3R1YWxfc2VyaWVzIDwtIGRmX1NHX21hY3JvW2RmX1NHX21hY3JvJGlzaW49PSJTRzFTODMwMDIzNDkiICYgZGZfU0dfbWFjcm8kZnllYXIgPCAyMDE3LF0kcmV2dF9sZWFkDQp1b2xfc2VyaWVzIDwtIHVvbFt1b2wkZnllYXIgPCAyMDE3LF0kcHJlZF91b2wNCmJhc2Vfc2VyaWVzIDwtIGRmX1NHX21hY3JvW2RmX1NHX21hY3JvJGlzaW49PSJTRzFTODMwMDIzNDkiICYgZGZfU0dfbWFjcm8kZnllYXIgPCAyMDE3LF0kcHJlZF9iYXNlDQptYWNyb19zZXJpZXMgPC0gZGZfU0dfbWFjcm9bZGZfU0dfbWFjcm8kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9TR19tYWNybyRmeWVhciA8IDIwMTcsXSRwcmVkX21hY3JvDQp3b3JsZF9zZXJpZXMgPC0gZGZfY2xlYW5bZGZfY2xlYW4kaXNpbj09IlNHMVM4MzAwMjM0OSIgJiBkZl9jbGVhbiRmeWVhciA8IDIwMTcsXSRwcmVkX3dvcmxkDQpgYGANCg0KYGBge3J9DQojIHNlcmllcyB2ZWN0b3JzIGNhbGN1bGF0ZWQgaGVyZSAtLSBTZWUgYXBwZW5kaXgNCnJtc2UgPC0gZnVuY3Rpb24odjEsIHYyKSB7DQogIHNxcnQobWVhbigodjEgLSB2MileMiwgbmEucm09VCkpDQp9DQoNCnJtc2UgPC0gYyhybXNlKGFjdHVhbF9zZXJpZXMsIHVvbF9zZXJpZXMpLCBybXNlKGFjdHVhbF9zZXJpZXMsIGJhc2Vfc2VyaWVzKSwNCiAgICAgICAgICBybXNlKGFjdHVhbF9zZXJpZXMsIG1hY3JvX3NlcmllcyksIHJtc2UoYWN0dWFsX3Nlcmllcywgd29ybGRfc2VyaWVzKSkNCm5hbWVzKHJtc2UpIDwtIGMoIlVPTCAyMDE4IFVPTCIsICJVT0wgMjAxOCBCYXNlIiwgIlVPTCAyMDE4IE1hY3JvIiwgIlVPTCAyMDE4IFdvcmxkIikNCnJtc2UNCmBgYA0KDQpgYGB7cn0NCnByZWRzDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KbGlicmFyeSh0aWR5dmVyc2UpICAjIEFzIGFsd2F5cw0KbGlicmFyeShwbG90bHkpICAjIGludGVyYWN0aXZlIGdyYXBocw0KbGlicmFyeShsdWJyaWRhdGUpICAjIGltcG9ydCBzb21lIHNlbnNpYmxlIGRhdGUgZnVuY3Rpb25zDQoNCiMgR2VuZXJhdGUgcXVhcnRlciBvdmVyIHF1YXJ0ZXIgZ3Jvd3RoICJyZXZ0cV9nciINCmRmIDwtIGRmICU+JSBncm91cF9ieShndmtleSkgJT4lIG11dGF0ZShyZXZ0cV9ncj1yZXZ0cSAvIGxhZyhyZXZ0cSkgLSAxKSAlPiUgdW5ncm91cCgpDQoNCiMgR2VuZXJhdGUgeWVhci1vdmVyLXllYXIgZ3Jvd3RoICJyZXZ0cV95b3kiDQpkZiA8LSBkZiAlPiUgZ3JvdXBfYnkoZ3ZrZXkpICU+JSBtdXRhdGUocmV2dHFfeW95PXJldnRxIC8gbGFnKHJldnRxLCA0KSAtIDEpICU+JSB1bmdyb3VwKCkNCg0KIyBHZW5lcmF0ZSBmaXJzdCBkaWZmZXJlbmNlICJyZXZ0cV9kIg0KZGYgPC0gZGYgJT4lIGdyb3VwX2J5KGd2a2V5KSAlPiUgbXV0YXRlKHJldnRxX2Q9cmV2dHEgLSBsYWcocmV2dHEpKSAlPiUgdW5ncm91cCgpDQoNCiMgR2VuZXJhdGUgYSBwcm9wZXIgZGF0ZQ0KIyBEYXRlIHdhcyBZWU1NRERzMTA6IFlZWVkvTU0vREQsIGNhbiBiZSBjb252ZXJ0ZWQgZnJvbSB0ZXh0IHRvIGRhdGUgZWFzaWx5DQpkZiRkYXRlIDwtIHltZChkZiRkYXRhZGF0ZSkgICMgRnJvbSBsdWJyaWRhdGUNCmRmJHF0ciA8LSBxdWFydGVyKGRmJGRhdGUpICAgIyBGcm9tIGx1YnJpZGF0ZQ0KYGBgDQoNCmBgYHtyfQ0KaHRtbF9kZihoZWFkKGRmWyxjKCJjb25tIiwiZGF0ZSIsInJldnRxIiwicmV2dHFfZ3IiLCAicmV2dHFfeW95IiwgInJldnRxX2QiKV0pKQ0KDQpoZWFkKGRmWyxjKCJjb25tIiwiZGF0ZSIsICJkYXRhZGF0ZSIpXSkNCmBgYA0KDQpgYGB7cn0NCiMgTWFrZSBhIGNvcHkgb2Ygb3VyIGRhdGEgZnJhbWUgdG8gY29tcGFyZSBsYXRlcg0KZGZfcHVycnIgPC0gZGYNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GfQ0KIyBBcHByb2FjaCAjMTogQWR2YW5jZWQgcHJvZ3JhbW1pbmcgdXNpbmcgcXVvc3VyZXMuLi4NCmxpYnJhcnkocmxhbmcpDQptdWx0aV9sYWcgPC0gZnVuY3Rpb24oZGYsIGxhZ3MsIHZhciwgcG9zdGZpeD0iIikgew0KICB2YXIgPC0gZW5xdW8odmFyKQ0KICBxdW9zdXJlcyA8LSBtYXAobGFncywgfnF1byhsYWcoISF2YXIsICEhLngpKSkgJT4lDQogICAgc2V0X25hbWVzKHBhc3RlMChxdW9fdGV4dCh2YXIpLCBwb3N0Zml4LCBsYWdzKSkNCiAgcmV0dXJuKHVuZ3JvdXAobXV0YXRlKGdyb3VwX2J5KGRmLCBndmtleSksICEhIXF1b3N1cmVzKSkpDQp9DQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Rn0NCiMgQXBwcm9hY2ggIzI6IE1peGluZyBwdXJyciB3aXRoIGRwbHlyIGFjcm9zcygpDQpsaWJyYXJ5KHB1cnJyKQ0KbXVsdGlfbGFnX3B1cnJyIDwtIGZ1bmN0aW9uKGRmLCBsYWdzLCB2YXIsIHBvc3RmaXg9IiIpIHsNCiAgbmV3X2NvbHVtbnMgPC0gDQogICAgbWFwX2RmYyhsYWdzLCANCiAgICAgIGZ1bmN0aW9uKHgpIGRmICU+JSANCiAgICAgICAgZ3JvdXBfYnkoZ3ZrZXkpICU+JQ0KICAgICAgICB0cmFuc211dGUoYWNyb3NzKGFsbF9vZih2YXIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIC5mbnMgPSBsaXN0KH4gbGFnKC4sIHgpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAubmFtZXMgPSBwYXN0ZTAoJ3tjb2x9JywgcG9zdGZpeCwgeCkgKSApICU+JQ0KICAgICAgICB1bmdyb3VwKCkgJT4lDQogICAgICAgIHNlbGVjdCgtZ3ZrZXkpKQ0KICBjYmluZChkZiwgbmV3X2NvbHVtbnMpDQp9DQpgYGANCg0KYGBge3J9DQpkZiA8LSBtdWx0aV9sYWcoZGYsIDE6OCwgcmV2dHEsICJfbCIpICAjIEdlbmVyYXRlIGxhZ3MgInJldnRxX2wjIg0KZGYgPC0gbXVsdGlfbGFnKGRmLCAxOjgsIHJldnRxX2dyKSAgICAgIyBHZW5lcmF0ZSBjaGFuZ2VzICJyZXZ0cV9nciMiDQpkZiA8LSBtdWx0aV9sYWcoZGYsIDE6OCwgcmV2dHFfeW95KSAgICAjIEdlbmVyYXRlIHllYXItb3Zlci15ZWFyIGNoYW5nZXMgInJldnRxX3lveSMiDQpkZiA8LSBtdWx0aV9sYWcoZGYsIDE6OCwgcmV2dHFfZCkgICAgICAjIEdlbmVyYXRlIGZpcnN0IGRpZmZlcmVuY2VzICJyZXZ0cV9kIyINCmBgYA0KDQpgYGB7cn0NCmRmX3B1cnJyIDwtIG11bHRpX2xhZ19wdXJycihkZl9wdXJyciwgMTo4LCAncmV2dHEnLCAiX2wiKSAgIyBHZW5lcmF0ZSBsYWdzICJyZXZ0cV9sIyINCmRmX3B1cnJyIDwtIG11bHRpX2xhZ19wdXJycihkZl9wdXJyciwgMTo4LCAncmV2dHFfZ3InKSAgICAgIyBHZW5lcmF0ZSBjaGFuZ2VzICJyZXZ0cV9nciMiDQpkZl9wdXJyciA8LSBtdWx0aV9sYWdfcHVycnIoZGZfcHVycnIsIDE6OCwgJ3JldnRxX3lveScpICAgICMgR2VuZXJhdGUgeWVhci1vdmVyLXllYXIgY2hhbmdlcyAicmV2dHFfeW95IyINCmRmX3B1cnJyIDwtIG11bHRpX2xhZ19wdXJycihkZl9wdXJyciwgMTo4LCAncmV2dHFfZCcpICAgICAgIyBHZW5lcmF0ZSBmaXJzdCBkaWZmZXJlbmNlcyAicmV2dHFfZCMiDQpgYGANCg0KYGBge3J9DQphbGwoZGY9PWRmX3B1cnJyLCBuYS5ybT1UKQ0KYGBgDQoNCmBgYHtyfQ0KaHRtbF9kZihoZWFkKGRmWyxjKCJjb25tIiwiZGF0ZSIsInJldnRxIiwicmV2dHFfbDEiLCAicmV2dHFfbDIiLCAicmV2dHFfbDMiLCAicmV2dHFfbDQiKV0pKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDbGVhbiB0aGUgZGF0YTogUmVwbGFjZSBOYU4sIEluZiwgYW5kIC1JbmYgd2l0aCBOQQ0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIH5yZXBsYWNlKC4sICFpcy5maW5pdGUoLiksIE5BKSkpDQoNCiMgU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhDQojIFRyYWluaW5nIGRhdGE6IFdlJ2xsIHVzZSBkYXRhIHJlbGVhc2VkIGJlZm9yZSAyMDE1DQp0cmFpbiA8LSBmaWx0ZXIoZGYsIHllYXIoZGF0ZSkgPCAyMDE1KQ0KDQojIFRlc3RpbmcgZGF0YTogV2UnbGwgdXNlIGRhdGEgcmVsZWFzZWQgMjAxNSB0aHJvdWdoIDIwMTgNCnRlc3QgPC0gZmlsdGVyKGRmLCB5ZWFyKGRhdGUpID49IDIwMTUpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0Kc3VtbWFyeShkZlssYygicmV2dHEiLCJyZXZ0cV9nciIsInJldnRxX3lveSIsICJyZXZ0cV9kIiwicXRyIildKQ0KYGBgDQoNCmBgYHtyfQ0KIyBUaGVzZSBmdW5jdGlvbnMgYXJlIGEgYml0IHVnbHksIGJ1dCBjYW4gY29uc3RydWN0IG1hbnkgY2hhcnRzIHF1aWNrbHkNCiMgZXZhbChwYXJzZSh0ZXh0PXZhcikpIGlzIGp1c3QgYSB3YXkgdG8gY29udmVydCB0aGUgc3RyaW5nIG5hbWUgdG8gYSB2YXJpYWJsZSByZWZlcmVuY2UNCiMgRGVuc2l0eSBwbG90IGZvciAxc3QgdG8gOTl0aCBwZXJjZW50aWxlIG9mIGRhdGENCnBsdF9kaXN0IDwtIGZ1bmN0aW9uKGRmLHZhcikgew0KICBkZiAlPiUNCiAgICBmaWx0ZXIoZXZhbChwYXJzZSh0ZXh0PXZhcikpIDwgcXVhbnRpbGUoZXZhbChwYXJzZSh0ZXh0PXZhcikpLDAuOTksIG5hLnJtPVRSVUUpLA0KICAgICAgICAgICBldmFsKHBhcnNlKHRleHQ9dmFyKSkgPiBxdWFudGlsZShldmFsKHBhcnNlKHRleHQ9dmFyKSksMC4wMSwgbmEucm09VFJVRSkpICU+JQ0KICAgIGdncGxvdChhZXMoeD1ldmFsKHBhcnNlKHRleHQ9dmFyKSkpKSArIA0KICAgIGdlb21fZGVuc2l0eSgpICsgeGxhYih2YXIpDQp9DQpgYGANCg0KYGBge3J9DQojIERlbnNpdHkgcGxvdCBmb3IgMXN0IHRvIDk5dGggcGVyY2VudGlsZSBvZiBib3RoIGNvbHVtbnMNCnBsdF9iYXIgPC0gZnVuY3Rpb24oZGYsdmFyKSB7DQogIGRmICU+JQ0KICAgIGZpbHRlcihldmFsKHBhcnNlKHRleHQ9dmFyKSkgPCBxdWFudGlsZShldmFsKHBhcnNlKHRleHQ9dmFyKSksMC45OSwgbmEucm09VFJVRSksDQogICAgICAgICAgIGV2YWwocGFyc2UodGV4dD12YXIpKSA+IHF1YW50aWxlKGV2YWwocGFyc2UodGV4dD12YXIpKSwwLjAxLCBuYS5ybT1UUlVFKSkgJT4lDQogICAgZ2dwbG90KGFlcyh5PWV2YWwocGFyc2UodGV4dD12YXIpKSwgeD1xdHIpKSArIA0KICAgIGdlb21fYmFyKHN0YXQgPSAic3VtbWFyeSIsIGZ1bi55ID0gIm1lYW4iKSArIHhsYWIodmFyKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KIyBTY2F0dGVyIHBsb3Qgd2l0aCBsYWcgZm9yIDFzdCB0byA5OXRoIHBlcmNlbnRpbGUgb2YgZGF0YQ0KcGx0X3NjdCA8LSBmdW5jdGlvbihkZix2YXIxLCB2YXIyKSB7DQogIGRmICU+JQ0KICAgIGZpbHRlcihldmFsKHBhcnNlKHRleHQ9dmFyMSkpIDwgcXVhbnRpbGUoZXZhbChwYXJzZSh0ZXh0PXZhcjEpKSwwLjk5LCBuYS5ybT1UUlVFKSwNCiAgICAgICAgICAgZXZhbChwYXJzZSh0ZXh0PXZhcjIpKSA8IHF1YW50aWxlKGV2YWwocGFyc2UodGV4dD12YXIyKSksMC45OSwgbmEucm09VFJVRSksDQogICAgICAgICAgIGV2YWwocGFyc2UodGV4dD12YXIxKSkgPiBxdWFudGlsZShldmFsKHBhcnNlKHRleHQ9dmFyMSkpLDAuMDEsIG5hLnJtPVRSVUUpLA0KICAgICAgICAgICBldmFsKHBhcnNlKHRleHQ9dmFyMikpID4gcXVhbnRpbGUoZXZhbChwYXJzZSh0ZXh0PXZhcjIpKSwwLjAxLCBuYS5ybT1UUlVFKSkgJT4lDQogICAgZ2dwbG90KGFlcyh5PWV2YWwocGFyc2UodGV4dD12YXIxKSksIHg9ZXZhbChwYXJzZSh0ZXh0PXZhcjIpKSwgY29sb3I9ZmFjdG9yKHF0cikpKSArIA0KICAgIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsgeWxhYih2YXIxKSArIHhsYWIodmFyMikNCn0NCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9M30NCnBsdF9kaXN0KHRyYWluLCAicmV2dHEiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD0zfQ0KcGx0X2Rpc3QodHJhaW4sICJyZXZ0cV9nciIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTN9DQpwbHRfZGlzdCh0cmFpbiwgInJldnRxX3lveSIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTN9DQpwbHRfZGlzdCh0cmFpbiwgInJldnRxX2QiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD0zfQ0KcGx0X2Jhcih0cmFpbiwgInJldnRxIikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9M30NCnBsdF9iYXIodHJhaW4sICJyZXZ0cV9nciIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTN9DQpwbHRfYmFyKHRyYWluLCAicmV2dHFfeW95IikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9M30NCnBsdF9iYXIodHJhaW4sICJyZXZ0cV9kIikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9M30NCnBsdF9zY3QodHJhaW4sICJyZXZ0cSIsICJyZXZ0cV9sMSIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTN9DQpwbHRfc2N0KHRyYWluLCAicmV2dHFfZ3IiLCAicmV2dHFfZ3IxIikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9M30NCnBsdF9zY3QodHJhaW4sICJyZXZ0cV95b3kiLCAicmV2dHFfeW95MSIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTN9DQpwbHRfc2N0KHRyYWluLCAicmV2dHFfZCIsICJyZXZ0cV9kMSIpDQpgYGANCg0KYGBge3J9DQpjb3IodHJhaW5bLGMoInJldnRxIiwicmV2dHFfbDEiLCJyZXZ0cV9sMiIsInJldnRxX2wzIiwgInJldnRxX2w0IildLA0KICAgIHVzZT0iY29tcGxldGUub2JzIikNCmBgYA0KDQpgYGB7cn0NCmNvcih0cmFpblssYygicmV2dHFfZ3IiLCJyZXZ0cV9ncjEiLCJyZXZ0cV9ncjIiLCJyZXZ0cV9ncjMiLCAicmV2dHFfZ3I0IildLA0KICAgIHVzZT0iY29tcGxldGUub2JzIikNCmBgYA0KDQpgYGB7cn0NCmNvcih0cmFpblssYygicmV2dHFfeW95IiwicmV2dHFfeW95MSIsInJldnRxX3lveTIiLCJyZXZ0cV95b3kzIiwgInJldnRxX3lveTQiKV0sDQogICAgdXNlPSJjb21wbGV0ZS5vYnMiKQ0KYGBgDQoNCmBgYHtyfQ0KY29yKHRyYWluWyxjKCJyZXZ0cV9kIiwicmV2dHFfZDEiLCJyZXZ0cV9kMiIsInJldnRxX2QzIiwgInJldnRxX2Q0IildLA0KICAgIHVzZT0iY29tcGxldGUub2JzIikNCmBgYA0KDQpgYGB7cn0NCm1vZDEgPC0gbG0ocmV2dHEgfiByZXZ0cV9sMSwgZGF0YT10cmFpbikNCmBgYA0KDQpgYGB7cn0NCm1vZDIgPC0gbG0ocmV2dHEgfiByZXZ0cV9sMSArIHJldnRxX2w0LCBkYXRhPXRyYWluKQ0KYGBgDQoNCmBgYHtyfQ0KbW9kMyA8LSBsbShyZXZ0cSB+IHJldnRxX2wxICsgcmV2dHFfbDIgKyByZXZ0cV9sMyArIHJldnRxX2w0ICsgcmV2dHFfbDUgKw0KICAgICAgICAgICByZXZ0cV9sNiArIHJldnRxX2w3ICsgcmV2dHFfbDgsIGRhdGE9dHJhaW4pDQpgYGANCg0KYGBge3J9DQptb2Q0IDwtIGxtKHJldnRxIH4gKHJldnRxX2wxICsgcmV2dHFfbDIgKyByZXZ0cV9sMyArIHJldnRxX2w0ICsgcmV2dHFfbDUgKw0KICAgICAgICAgICAgICAgICAgICByZXZ0cV9sNiArIHJldnRxX2w3ICsgcmV2dHFfbDgpOmZhY3RvcihxdHIpLCBkYXRhPXRyYWluKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2QxKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2QyKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2QzKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtb2Q0KQ0KYGBgDQoNCmBgYHtyfQ0Kcm1zZSA8LSBmdW5jdGlvbih2MSwgdjIpIHsNCiAgc3FydChtZWFuKCh2MSAtIHYyKV4yLCBuYS5ybT1UKSkNCn0NCmBgYA0KDQpgYGB7cn0NCm1hZSA8LSBmdW5jdGlvbih2MSwgdjIpIHsNCiAgbWVhbihhYnModjEtdjIpLCBuYS5ybT1UKQ0KfQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCm1vZGVscyA8LSBsaXN0KG1vZDEsbW9kMixtb2QzLG1vZDQpDQptb2RlbF9uYW1lcyA8LSBjKCIxIHBlcmlvZCIsICIxIGFuZCA0IHBlcmlvZHMiLCAiOCBwZXJpb2RzIiwgIjggcGVyaW9kcyB3LyBxdWFydGVycyIpDQoNCmRmX3Rlc3QgPC0gZGF0YS5mcmFtZShhZGpfcl9zcT1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXN1bW1hcnkoeClbWyJhZGouci5zcXVhcmVkIl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0cmFpbiRyZXZ0cSwgcHJlZGljdCh4LHRyYWluKSkpLA0KICAgICAgICAgICAgICAgICAgICAgIG1hZV9pbj1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0cmFpbiRyZXZ0cSwgcHJlZGljdCh4LHRyYWluKSkpLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2Vfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0ZXN0JHJldnRxLCBwcmVkaWN0KHgsdGVzdCkpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHEsIHByZWRpY3QoeCx0ZXN0KSkpKQ0Kcm93bmFtZXMoZGZfdGVzdCkgPC0gbW9kZWxfbmFtZXMNCmh0bWxfZGYoZGZfdGVzdCkgICMgQ3VzdG9tIGZ1bmN0aW9uIHVzaW5nIGtuaXRyIGFuZCBrYWJsZUV4dHJhDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD00LjV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD1wcmVkaWN0KG1vZDEsdGVzdCksIGNvbG9yPWZhY3RvcihxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIikgKyANCiAgeGxhYigiUHJlZGljdGlvbjogMSBwZXJpb2QgbW9kZWwiKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NC41fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxLHg9cHJlZGljdChtb2Q0LHRlc3QpLCBjb2xvcj1mYWN0b3IocXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDggcGVyaW9kIFggcXVhcnRlciBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KIyBtb2RlbHMNCm1vZDFnIDwtIGxtKHJldnRxX2dyIH4gcmV2dHFfZ3IxLCBkYXRhPXRyYWluKQ0KbW9kMmcgPC0gbG0ocmV2dHFfZ3IgfiByZXZ0cV9ncjEgKyByZXZ0cV9ncjQsIGRhdGE9dHJhaW4pDQptb2QzZyA8LSBsbShyZXZ0cV9nciB+IHJldnRxX2dyMSArIHJldnRxX2dyMiArIHJldnRxX2dyMyArIHJldnRxX2dyNCArIHJldnRxX2dyNSArIHJldnRxX2dyNiArIHJldnRxX2dyNyArIHJldnRxX2dyOCwgZGF0YT10cmFpbikNCm1vZDRnIDwtIGxtKHJldnRxX2dyIH4gKHJldnRxX2dyMSArIHJldnRxX2dyMiArIHJldnRxX2dyMyArIHJldnRxX2dyNCArIHJldnRxX2dyNSArIHJldnRxX2dyNiArIHJldnRxX2dyNyArIHJldnRxX2dyOCk6ZmFjdG9yKHF0ciksIGRhdGE9dHJhaW4pDQoNCm1vZGVscyA8LSBsaXN0KG1vZDFnLCBtb2QyZywgbW9kM2csIG1vZDRnKQ0KbW9kZWxfbmFtZXMgPC0gYygiMSBwZXJpb2QiLCAiMSBhbmQgNCBwZXJpb2RzIiwgIjggcGVyaW9kcyIsICI4IHBlcmlvZHMgdy8gcXVhcnRlcnMiKQ0KDQpkZl90ZXN0IDwtIGRhdGEuZnJhbWUoYWRqX3Jfc3E9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClzdW1tYXJ5KHgpW1siYWRqLnIuc3F1YXJlZCJdXSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9pbj1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXJtc2UodHJhaW4kcmV2dHEsICgxK3ByZWRpY3QoeCx0cmFpbikpKnRyYWluJHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxLCAoMStwcmVkaWN0KHgsdHJhaW4pKSp0cmFpbiRyZXZ0cV9sMSkpLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2Vfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0ZXN0JHJldnRxLCAoMStwcmVkaWN0KHgsdGVzdCkpKnRlc3QkcmV2dHFfbDEpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHEsICgxK3ByZWRpY3QoeCx0ZXN0KSkqdGVzdCRyZXZ0cV9sMSkpKQ0Kcm93bmFtZXMoZGZfdGVzdCkgPC0gbW9kZWxfbmFtZXMNCmh0bWxfZGYoZGZfdGVzdCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD0oMStwcmVkaWN0KG1vZDFnLHRlc3QpKSp0ZXN0JHJldnRxX2wxLCBjb2xvcj1mYWN0b3IocXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDEgcGVyaW9kIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD0oMStwcmVkaWN0KG1vZDRnLHRlc3QpKSp0ZXN0JHJldnRxX2wxLCBjb2xvcj1mYWN0b3IocXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDggcGVyaW9kIFggcXVhcnRlciBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KIyBtb2RlbHMNCm1vZDF5IDwtIGxtKHJldnRxX3lveSB+IHJldnRxX3lveTEsIGRhdGE9dHJhaW4pDQptb2QyeSA8LSBsbShyZXZ0cV95b3kgfiByZXZ0cV95b3kxICsgcmV2dHFfeW95NCwgZGF0YT10cmFpbikNCm1vZDN5IDwtIGxtKHJldnRxX3lveSB+IHJldnRxX3lveTEgKyByZXZ0cV95b3kyICsgcmV2dHFfeW95MyArIHJldnRxX3lveTQgKyByZXZ0cV95b3k1ICsgcmV2dHFfeW95NiArIHJldnRxX3lveTcgKyByZXZ0cV95b3k4LCBkYXRhPXRyYWluKQ0KbW9kNHkgPC0gbG0ocmV2dHFfZ3IgfiAocmV2dHFfeW95MSArIHJldnRxX3lveTIgKyByZXZ0cV95b3kzICsgcmV2dHFfeW95NCArIHJldnRxX3lveTUgKyByZXZ0cV95b3k2ICsgcmV2dHFfeW95NyArIHJldnRxX3lveTgpOmZhY3RvcihxdHIpLCBkYXRhPXRyYWluKQ0KDQptb2RlbHMgPC0gbGlzdChtb2QxeSwgbW9kMnksIG1vZDN5LCBtb2Q0eSkNCm1vZGVsX25hbWVzIDwtIGMoIjEgcGVyaW9kIiwgIjEgYW5kIDQgcGVyaW9kcyIsICI4IHBlcmlvZHMiLCAiOCBwZXJpb2RzIHcvIHF1YXJ0ZXJzIikNCg0KZGZfdGVzdCA8LSBkYXRhLmZyYW1lKGFkal9yX3NxPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpc3VtbWFyeSh4KVtbImFkai5yLnNxdWFyZWQiXV0pLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2VfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRyYWluJHJldnRxLCAoMStwcmVkaWN0KHgsdHJhaW4pKSp0cmFpbiRyZXZ0cV9sNCkpLA0KICAgICAgICAgICAgICAgICAgICAgIG1hZV9pbj1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0cmFpbiRyZXZ0cSwgKDErcHJlZGljdCh4LHRyYWluKSkqdHJhaW4kcmV2dHFfbDQpKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXJtc2UodGVzdCRyZXZ0cSwgKDErcHJlZGljdCh4LHRlc3QpKSp0ZXN0JHJldnRxX2w0KSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0ZXN0JHJldnRxLCAoMStwcmVkaWN0KHgsdGVzdCkpKnRlc3QkcmV2dHFfbDQpKSkNCnJvd25hbWVzKGRmX3Rlc3QpIDwtIG1vZGVsX25hbWVzDQpodG1sX2RmKGRmX3Rlc3QpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxLHg9KDErcHJlZGljdChtb2QxeSx0ZXN0KSkqdGVzdCRyZXZ0cV9sNCwgY29sb3I9ZmFjdG9yKHF0cikpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsgZ2VvbV9wb2ludCgpICsNCiAgeWxhYigiQWN0dWFsIHJldmVudWUiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiAxIHBlcmlvZCBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxLHg9KDErcHJlZGljdChtb2QzeSx0ZXN0KSkqdGVzdCRyZXZ0cV9sNCwgY29sb3I9ZmFjdG9yKHF0cikpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsgZ2VvbV9wb2ludCgpICsNCiAgeWxhYigiQWN0dWFsIHJldmVudWUiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiA4IHBlcmlvZCBtb2RlbCIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KIyBtb2RlbHMNCm1vZDFkIDwtIGxtKHJldnRxX2QgfiByZXZ0cV9kMSwgZGF0YT10cmFpbikNCm1vZDJkIDwtIGxtKHJldnRxX2QgfiByZXZ0cV9kMSArIHJldnRxX2Q0LCBkYXRhPXRyYWluKQ0KbW9kM2QgPC0gbG0ocmV2dHFfZCB+IHJldnRxX2QxICsgcmV2dHFfZDIgKyByZXZ0cV9kMyArIHJldnRxX2Q0ICsgcmV2dHFfZDUgKyByZXZ0cV9kNiArIHJldnRxX2Q3ICsgcmV2dHFfZDgsIGRhdGE9dHJhaW4pDQptb2Q0ZCA8LSBsbShyZXZ0cV9kIH4gKHJldnRxX2QxICsgcmV2dHFfZDIgKyByZXZ0cV9kMyArIHJldnRxX2Q0ICsgcmV2dHFfZDUgKyByZXZ0cV9kNiArIHJldnRxX2Q3ICsgcmV2dHFfZDgpOmZhY3RvcihxdHIpLCBkYXRhPXRyYWluKQ0KDQptb2RlbHMgPC0gbGlzdChtb2QxZCwgbW9kMmQsIG1vZDNkLCBtb2Q0ZCkNCm1vZGVsX25hbWVzIDwtIGMoIjEgcGVyaW9kIiwgIjEgYW5kIDQgcGVyaW9kcyIsICI4IHBlcmlvZHMiLCAiOCBwZXJpb2RzIHcvIHF1YXJ0ZXJzIikNCg0KZGZfdGVzdCA8LSBkYXRhLmZyYW1lKGFkal9yX3NxPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpc3VtbWFyeSh4KVtbImFkai5yLnNxdWFyZWQiXV0pLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2VfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRyYWluJHJldnRxLCBwcmVkaWN0KHgsdHJhaW4pK3RyYWluJHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxLCBwcmVkaWN0KHgsdHJhaW4pK3RyYWluJHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRlc3QkcmV2dHEsIHByZWRpY3QoeCx0ZXN0KSt0ZXN0JHJldnRxX2wxKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KW1hZSh0ZXN0JHJldnRxLCBwcmVkaWN0KHgsdGVzdCkrdGVzdCRyZXZ0cV9sMSkpKQ0Kcm93bmFtZXMoZGZfdGVzdCkgPC0gbW9kZWxfbmFtZXMNCmh0bWxfZGYoZGZfdGVzdCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHEseD1wcmVkaWN0KG1vZDFkLHRlc3QpK3Rlc3QkcmV2dHFfbDEsIGNvbG9yPWZhY3RvcihxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIikgKyANCiAgeGxhYigiUHJlZGljdGlvbjogMSBwZXJpb2QgbW9kZWwiKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NX0NCnRlc3QgJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0cSx4PXByZWRpY3QobW9kNGQsdGVzdCkrdGVzdCRyZXZ0cV9sMSwgY29sb3I9ZmFjdG9yKHF0cikpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsgZ2VvbV9wb2ludCgpICsNCiAgeWxhYigiQWN0dWFsIHJldmVudWUiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiA4IHBlcmlvZCBYIHF1YXJ0ZXIgbW9kZWwiKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCiMgbW9kZWxzDQptb2QxZyA8LSBsbShyZXZ0cV9nciB+IHJldnRxX2dyMSwgZGF0YT10cmFpbikNCm1vZDJnIDwtIGxtKHJldnRxX2dyIH4gcmV2dHFfZ3IxICsgcmV2dHFfZ3I0LCBkYXRhPXRyYWluKQ0KbW9kM2cgPC0gbG0ocmV2dHFfZ3IgfiByZXZ0cV9ncjEgKyByZXZ0cV9ncjIgKyByZXZ0cV9ncjMgKyByZXZ0cV9ncjQgKyByZXZ0cV9ncjUgKyByZXZ0cV9ncjYgKyByZXZ0cV9ncjcgKyByZXZ0cV9ncjgsIGRhdGE9dHJhaW4pDQptb2Q0ZyA8LSBsbShyZXZ0cV9nciB+IChyZXZ0cV9ncjEgKyByZXZ0cV9ncjIgKyByZXZ0cV9ncjMgKyByZXZ0cV9ncjQgKyByZXZ0cV9ncjUgKyByZXZ0cV9ncjYgKyByZXZ0cV9ncjcgKyByZXZ0cV9ncjgpOmZhY3RvcihxdHIpLCBkYXRhPXRyYWluKQ0KDQptb2RlbHMgPC0gbGlzdChtb2QxZywgbW9kMmcsIG1vZDNnLCBtb2Q0ZykNCm1vZGVsX25hbWVzIDwtIGMoIjEgcGVyaW9kIiwgIjEgYW5kIDQgcGVyaW9kcyIsICI4IHBlcmlvZHMiLCAiOCBwZXJpb2RzIHcvIHF1YXJ0ZXJzIikNCg0KZGZfdGVzdCA8LSBkYXRhLmZyYW1lKGFkal9yX3NxPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpc3VtbWFyeSh4KVtbImFkai5yLnNxdWFyZWQiXV0pLA0KICAgICAgICAgICAgICAgICAgICAgIHJtc2VfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRyYWluJHJldnRxX2dyLCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxX2dyLCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRlc3QkcmV2dHFfZ3IsIHByZWRpY3QoeCx0ZXN0KSkpLA0KICAgICAgICAgICAgICAgICAgICAgIG1hZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeCltYWUodGVzdCRyZXZ0cV9nciwgcHJlZGljdCh4LHRlc3QpKSkpDQpyb3duYW1lcyhkZl90ZXN0KSA8LSBtb2RlbF9uYW1lcw0KaHRtbF9kZihkZl90ZXN0KQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NX0NCnRlc3QgJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0cV9ncix4PXByZWRpY3QobW9kMWcsdGVzdCksIGNvbG9yPWZhY3RvcihxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIGdyb3d0aCIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDEgcGVyaW9kIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHFfZ3IseD1wcmVkaWN0KG1vZDRnLHRlc3QpLCBjb2xvcj1mYWN0b3IocXRyKSkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyBnZW9tX3BvaW50KCkgKw0KICB5bGFiKCJBY3R1YWwgcmV2ZW51ZSBncm93dGgiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiA4IHBlcmlvZCBYIHF1YXJ0ZXIgbW9kZWwiKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCiMgbW9kZWxzDQptb2QxeSA8LSBsbShyZXZ0cV95b3kgfiByZXZ0cV95b3kxLCBkYXRhPXRyYWluKQ0KbW9kMnkgPC0gbG0ocmV2dHFfeW95IH4gcmV2dHFfeW95MSArIHJldnRxX3lveTQsIGRhdGE9dHJhaW4pDQptb2QzeSA8LSBsbShyZXZ0cV95b3kgfiByZXZ0cV95b3kxICsgcmV2dHFfeW95MiArIHJldnRxX3lveTMgKyByZXZ0cV95b3k0ICsgcmV2dHFfeW95NSArIHJldnRxX3lveTYgKyByZXZ0cV95b3k3ICsgcmV2dHFfeW95OCwgZGF0YT10cmFpbikNCm1vZDR5IDwtIGxtKHJldnRxX2dyIH4gKHJldnRxX3lveTEgKyByZXZ0cV95b3kyICsgcmV2dHFfeW95MyArIHJldnRxX3lveTQgKyByZXZ0cV95b3k1ICsgcmV2dHFfeW95NiArIHJldnRxX3lveTcgKyByZXZ0cV95b3k4KTpmYWN0b3IocXRyKSwgZGF0YT10cmFpbikNCg0KbW9kZWxzIDwtIGxpc3QobW9kMXksIG1vZDJ5LCBtb2QzeSwgbW9kNHkpDQptb2RlbF9uYW1lcyA8LSBjKCIxIHBlcmlvZCIsICIxIGFuZCA0IHBlcmlvZHMiLCAiOCBwZXJpb2RzIiwgIjggcGVyaW9kcyB3LyBxdWFydGVycyIpDQoNCmRmX3Rlc3QgPC0gZGF0YS5mcmFtZShhZGpfcl9zcT1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXN1bW1hcnkoeClbWyJhZGouci5zcXVhcmVkIl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0cmFpbiRyZXZ0cV95b3ksIHByZWRpY3QoeCx0cmFpbikpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfaW49c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeCltYWUodHJhaW4kcmV2dHFfeW95LCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgcm1zZV9vdXQ9c2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeClybXNlKHRlc3QkcmV2dHFfeW95LCBwcmVkaWN0KHgsdGVzdCkpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHFfeW95LCBwcmVkaWN0KHgsdGVzdCkpKSkNCnJvd25hbWVzKGRmX3Rlc3QpIDwtIG1vZGVsX25hbWVzDQpodG1sX2RmKGRmX3Rlc3QpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD01fQ0KdGVzdCAlPiUNCiAgZ2dwbG90KGFlcyh5PXJldnRxX3lveSx4PXByZWRpY3QobW9kMXksdGVzdCksIGNvbG9yPWZhY3RvcihxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCB5ZWFyIG92ZXIgeWVhciByZXZlbnVlIGdyb3d0aCIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDEgcGVyaW9kIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHFfeW95LHg9cHJlZGljdChtb2QzeSx0ZXN0KSwgY29sb3I9ZmFjdG9yKHF0cikpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsgZ2VvbV9wb2ludCgpICsNCiAgeWxhYigiQWN0dWFsIHllYXIgb3ZlciB5ZWFyIHJldmVudWUgZ3Jvd3RoIikgKyANCiAgeGxhYigiUHJlZGljdGlvbjogOCBwZXJpb2QgbW9kZWwiKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCiMgbW9kZWxzDQptb2QxZCA8LSBsbShyZXZ0cV9kIH4gcmV2dHFfZDEsIGRhdGE9dHJhaW4pDQptb2QyZCA8LSBsbShyZXZ0cV9kIH4gcmV2dHFfZDEgKyByZXZ0cV9kNCwgZGF0YT10cmFpbikNCm1vZDNkIDwtIGxtKHJldnRxX2QgfiByZXZ0cV9kMSArIHJldnRxX2QyICsgcmV2dHFfZDMgKyByZXZ0cV9kNCArIHJldnRxX2Q1ICsgcmV2dHFfZDYgKyByZXZ0cV9kNyArIHJldnRxX2Q4LCBkYXRhPXRyYWluKQ0KbW9kNGQgPC0gbG0ocmV2dHFfZCB+IChyZXZ0cV9kMSArIHJldnRxX2QyICsgcmV2dHFfZDMgKyByZXZ0cV9kNCArIHJldnRxX2Q1ICsgcmV2dHFfZDYgKyByZXZ0cV9kNyArIHJldnRxX2Q4KTpmYWN0b3IocXRyKSwgZGF0YT10cmFpbikNCg0KbW9kZWxzIDwtIGxpc3QobW9kMWQsIG1vZDJkLCBtb2QzZCwgbW9kNGQpDQptb2RlbF9uYW1lcyA8LSBjKCIxIHBlcmlvZCIsICIxIGFuZCA0IHBlcmlvZHMiLCAiOCBwZXJpb2RzIiwgIjggcGVyaW9kcyB3LyBxdWFydGVycyIpDQoNCmRmX3Rlc3QgPC0gZGF0YS5mcmFtZShhZGpfcl9zcT1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXN1bW1hcnkoeClbWyJhZGouci5zcXVhcmVkIl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpcm1zZSh0cmFpbiRyZXZ0cV9kLCBwcmVkaWN0KHgsdHJhaW4pKSksDQogICAgICAgICAgICAgICAgICAgICAgbWFlX2luPXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRyYWluJHJldnRxX2QsIHByZWRpY3QoeCx0cmFpbikpKSwNCiAgICAgICAgICAgICAgICAgICAgICBybXNlX291dD1zYXBwbHkobW9kZWxzLCBmdW5jdGlvbih4KXJtc2UodGVzdCRyZXZ0cV9kLCBwcmVkaWN0KHgsdGVzdCkpKSwNCiAgICAgICAgICAgICAgICAgICAgICBtYWVfb3V0PXNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpbWFlKHRlc3QkcmV2dHFfZCwgcHJlZGljdCh4LHRlc3QpKSkpDQpyb3duYW1lcyhkZl90ZXN0KSA8LSBtb2RlbF9uYW1lcw0KaHRtbF9kZihkZl90ZXN0KQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9NX0NCnRlc3QgJT4lDQogIGdncGxvdChhZXMoeT1yZXZ0cV9kLHg9cHJlZGljdChtb2QxZCx0ZXN0KSwgY29sb3I9ZmFjdG9yKHF0cikpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsgZ2VvbV9wb2ludCgpICsNCiAgeWxhYigiQWN0dWFsIHJldmVudWUgZmlyc3QgZGlmZmVyZW5jZSIpICsgDQogIHhsYWIoIlByZWRpY3Rpb246IDEgcGVyaW9kIG1vZGVsIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTV9DQp0ZXN0ICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cmV2dHFfZCx4PXByZWRpY3QobW9kNGQsdGVzdCksIGNvbG9yPWZhY3RvcihxdHIpKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIGdlb21fcG9pbnQoKSArDQogIHlsYWIoIkFjdHVhbCByZXZlbnVlIGZpcnN0IGRpZmZlcmVuY2UiKSArIA0KICB4bGFiKCJQcmVkaWN0aW9uOiA4IHBlcmlvZCBYIHF1YXJ0ZXIgbW9kZWwiKQ0KYGBgDQoNCg==