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==