Note that the directories used to store data are likely different on your computer, and such references will need to be changed before using any such code.
library(knitr)
library(kableExtra)
html_df <- function(text, cols=NULL, col1=FALSE, full=F) {
if(!length(cols)) {
cols=colnames(text)
}
if(!col1) {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full)
} else {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full) %>%
column_spec(1,bold=T)
}
}
library(tidyverse)
library(plotly)
library(lubridate)
df <- read.csv("../../Data/Session_5-1.csv", stringsAsFactors=FALSE)
df_ratings <- read.csv("../../Data/Session_5-2.csv", stringsAsFactors=FALSE)
df_mve <- read.csv("../../Data/Session_5-3.csv", stringsAsFactors=FALSE)
df_rf <- read.csv("../../Data/Session_5-4.csv", stringsAsFactors=FALSE)
df_stock <- read.csv("../../Data/Session_5-5.csv", stringsAsFactors=FALSE)
# initial cleaning
# 100338 is an outlier in the bonds distribution
df <- df %>% filter(at >= 1, revt >= 1, gvkey != 100338)
## Merge in stock value
df$date <- as.Date(df$datadate)
df_mve <- df_mve %>%
mutate(date = as.Date(datadate),
mve = csho * prcc_f) %>%
rename(gvkey=GVKEY)
df <- left_join(df, df_mve[,c("gvkey","date","mve")])
Joining, by = c("gvkey", "date")
df <- df %>%
group_by(gvkey) %>%
mutate(bankrupt = ifelse(row_number() == n() & dlrsn == 2 &
!is.na(dlrsn), 1, 0)) %>%
ungroup()
# Calculate the measures needed
df <- df %>%
mutate(wcap_at = wcap / at, # x1
re_at = re / at, # x2
ebit_at = ebit / at, # x3
mve_lt = mve / lt, # x4
revt_at = revt / at) # x5
# cleanup
df <- df %>%
mutate_if(is.numeric, list(~replace(., !is.finite(.), NA)))
# Calculate the score
df <- df %>%
mutate(Z = 1.2 * wcap_at + 1.4 * re_at + 3.3 * ebit_at + 0.6 * mve_lt +
0.999 * revt_at)
# Calculate date info for merging
df$date <- as.Date(df$datadate)
df$year <- year(df$date)
df$month <- month(df$date)
# df_ratings has ratings data in it
# Ratings, in order from worst to best
ratings <- c("D", "C", "CC", "CCC-", "CCC","CCC+", "B-", "B", "B+", "BB-",
"BB", "BB+", "BBB-", "BBB", "BBB+", "A-", "A", "A+", "AA-", "AA",
"AA+", "AAA-", "AAA", "AAA+")
# Convert string ratings (splticrm) to numeric ratings
df_ratings$rating <- factor(df_ratings$splticrm, levels=ratings, ordered=T)
df_ratings$date <- as.Date(df_ratings$datadate)
df_ratings$year <- year(df_ratings$date)
df_ratings$month <- month(df_ratings$date)
# Merge together data
df <- left_join(df, df_ratings[,c("gvkey", "year", "month", "rating")])
Joining, by = c("gvkey", "year", "month")
plot <- df %>%
filter(!is.na(Z), !is.na(rating)) %>%
group_by(rating) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(rating, mean_Z) %>%
ggplot(aes(y=mean_Z, x=rating)) +
geom_col() +
ylab('Mean Altman Z') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(Z),
!is.na(bankrupt)) %>%
group_by(bankrupt) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_Z) %>%
html_df()
bankrupt |
mean_Z |
0 |
3.939223 |
1 |
0.927843 |
plot <- df %>%
filter(!is.na(Z), !is.na(rating), year >= 2000) %>%
group_by(rating) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(rating, mean_Z) %>%
ggplot(aes(y=mean_Z, x=rating)) +
geom_col() +
ylab('Mean Altman Z') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(Z),
!is.na(bankrupt),
year >= 2000) %>%
group_by(bankrupt) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_Z) %>%
html_df()
bankrupt |
mean_Z |
0 |
3.822281 |
1 |
1.417683 |
fit_Z <- glm(bankrupt ~ Z, data=df, family=binomial)
summary(fit_Z)
Call:
glm(formula = bankrupt ~ Z, family = binomial, data = df)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.8297 -0.0676 -0.0654 -0.0624 3.7794
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -5.94354 0.11829 -50.245 < 2e-16 ***
Z -0.06383 0.01239 -5.151 2.59e-07 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1085.2 on 35296 degrees of freedom
Residual deviance: 1066.5 on 35295 degrees of freedom
(15577 observations deleted due to missingness)
AIC: 1070.5
Number of Fisher Scoring iterations: 9
library(ROCR)
dfZ <- df %>% filter(!is.na(Z), !is.na(bankrupt))
pred_Z <- predict(fit_Z, dfZ, type="response")
ROCpred_Z <- prediction(as.numeric(pred_Z), as.numeric(dfZ$bankrupt))
ROCperf_Z <- performance(ROCpred_Z, 'tpr','fpr')
df_ROC_Z <- data.frame(
FP=c(ROCperf_Z@x.values[[1]]),
TP=c(ROCperf_Z@y.values[[1]]))
ggplot(data=df_ROC_Z,
aes(x=FP, y=TP)) + geom_line() +
geom_abline(slope=1)

plot(ROCperf_Z)

ggplot(data=df_ROC_Z, aes(x=FP, y=TP)) +
geom_line() +
geom_abline(slope=1) +
ylab("True positive rate (Sensitivity)") +
xlab("False positive rate (1 - Specificity)") +
ggtitle("ROC Curve")

auc_Z <- performance(ROCpred_Z, measure = "auc")
auc_Z@y.values[[1]]
[1] 0.8280943
score = 1
m = 0
std = 1
funcShaded <- function(x, lower_bound) {
y = dnorm(x, mean = m, sd = std)
y[x < lower_bound] <- NA
return(y)
}
ggplot(data.frame(x = c(-3, 3)), aes(x = x)) +
stat_function(fun = dnorm, args = list(mean = m, sd = std)) +
stat_function(fun = funcShaded, args = list(lower_bound = score),
geom = "area", fill = 'black', alpha = .2) +
scale_x_continuous(name = "Score", breaks = seq(-3, 3, std)) +
geom_text(data = data.frame(x=c(1.5), y=c(0.05)), aes(x=x, y=y, label="Prob(default)", size=30)) +
geom_line(data = data.frame(x=c(1,1), y=c(0,0.4)), aes(x=x,y=y)) +
geom_text(data = data.frame(x=c(1.3), y=c(0.4)), aes(x=x, y=y, label="DD", size=30)) +
theme(legend.position="none")

# df_stock is an already prepped csv from CRSP data
df_stock$date <- as.Date(df_stock$date)
df <- left_join(df, df_stock[,c("gvkey", "date", "ret", "ret.sd")])
Joining, by = c("gvkey", "date")
df_rf$date <- as.Date(df_rf$dateff)
df_rf$year <- year(df_rf$date)
df_rf$month <- month(df_rf$date)
df <- left_join(df, df_rf[,c("year", "month", "rf")])
Joining, by = c("year", "month")
df <- df %>%
mutate(DD = (log(mve / lt) + (rf - (ret.sd*sqrt(253))^2 / 2)) /
(ret.sd*sqrt(253)))
# Clean the measure
df <- df %>%
mutate_if(is.numeric, list(~replace(., !is.finite(.), NA)))
plot <- df %>%
filter(!is.na(DD),
!is.na(rating)) %>%
group_by(rating) %>%
mutate(mean_DD=mean(DD,na.rm=T),
prob_default = pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(rating, prob_default) %>%
ggplot(aes(y=prob_default, x=rating)) +
geom_col() +
ylab('Probability of default') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(DD),
!is.na(bankrupt)) %>%
group_by(bankrupt) %>%
mutate(mean_DD=mean(DD, na.rm=T),
prob_default =
pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_DD,
prob_default) %>%
html_df()
bankrupt |
mean_DD |
prob_default |
0 |
0.6096854 |
0.2710351 |
1 |
-2.4445081 |
0.9927475 |
plot <- df %>%
filter(!is.na(DD),
!is.na(rating),
year >= 2000) %>%
group_by(rating) %>%
mutate(mean_DD=mean(DD,na.rm=T),
prob_default = pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(rating, prob_default) %>%
ggplot(aes(y=prob_default, x=rating)) +
geom_col() +
ylab('Probability of default') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(DD),
!is.na(bankrupt),
year >= 2000) %>%
group_by(bankrupt) %>%
mutate(mean_DD=mean(DD, na.rm=T),
prob_default =
pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_DD,
prob_default) %>%
html_df()
bankrupt |
mean_DD |
prob_default |
0 |
0.8379932 |
0.2010172 |
1 |
-4.3001844 |
0.9999915 |
fit_DD <- glm(bankrupt ~ DD, data=df, family=binomial)
summary(fit_DD)
Call:
glm(formula = bankrupt ~ DD, family = binomial, data = df)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.9929 -0.0750 -0.0634 -0.0506 3.6503
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -6.16394 0.15322 -40.230 < 2e-16 ***
DD -0.24459 0.03781 -6.469 9.89e-11 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 718.67 on 21563 degrees of freedom
Residual deviance: 677.27 on 21562 degrees of freedom
(33618 observations deleted due to missingness)
AIC: 681.27
Number of Fisher Scoring iterations: 9
dfDD <- df %>% filter(!is.na(DD), !is.na(bankrupt))
pred_DD <- predict(fit_DD, dfDD, type="response")
ROCpred_DD <- prediction(as.numeric(pred_DD), as.numeric(dfDD$bankrupt))
ROCperf_DD <- performance(ROCpred_DD, 'tpr','fpr')
df_ROC_DD <- data.frame(FalsePositive=c(ROCperf_DD@x.values[[1]]),
TruePositive=c(ROCperf_DD@y.values[[1]]))
ggplot() +
geom_line(data=df_ROC_DD, aes(x=FalsePositive, y=TruePositive, color="DD")) +
geom_line(data=df_ROC_Z, aes(x=FP, y=TP, color="Z")) +
geom_abline(slope=1)

#AUC
auc_DD <- performance(ROCpred_DD, measure = "auc")
AUCs <- c(auc_Z@y.values[[1]], auc_DD@y.values[[1]])
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.8280943 0.8098304
# calculate downgrade
df <- df %>%
group_by(gvkey) %>%
arrange(date) %>%
mutate(downgrade = ifelse(rating < lag(rating),1,0),
diff_Z = Z - lag(Z),
diff_DD = DD - lag(DD)) %>%
ungroup()
# training sample
train <- df %>% filter(year < 2014, !is.na(diff_Z), !is.na(diff_DD), !is.na(downgrade),
year > 1985)
test <- df %>% filter(year >= 2014, !is.na(diff_Z), !is.na(diff_DD), !is.na(downgrade))
# glms
fit_Z2 <- glm(downgrade ~ diff_Z, data=train, family=binomial)
fit_DD2 <- glm(downgrade ~ diff_DD, data=train, family=binomial)
summary(fit_Z2)
Call:
glm(formula = downgrade ~ diff_Z, family = binomial, data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-3.4115 -0.4428 -0.4428 -0.3928 2.7437
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.27310 0.06139 -37.029 <2e-16 ***
diff_Z -0.77150 0.09245 -8.345 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2145.3 on 3277 degrees of freedom
Residual deviance: 2065.8 on 3276 degrees of freedom
AIC: 2069.8
Number of Fisher Scoring iterations: 5
summary(fit_DD2)
Call:
glm(formula = downgrade ~ diff_DD, family = binomial, data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.5726 -0.4565 -0.4558 -0.4095 2.6804
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.21199 0.05926 -37.325 < 2e-16 ***
diff_DD -0.21378 0.03723 -5.742 9.37e-09 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2145.3 on 3277 degrees of freedom
Residual deviance: 2113.2 on 3276 degrees of freedom
AIC: 2117.2
Number of Fisher Scoring iterations: 5
pred_Z2 <- predict(fit_Z2, train, type="response")
pred_Z2 <- ifelse(!is.finite(pred_Z2),NA,pred_Z2)
ROCpred_Z2 <- prediction(as.numeric(pred_Z2[!is.na(train$downgrade) & !is.na(pred_Z2)]), as.numeric(train[!is.na(train$downgrade) & !is.na(pred_Z2),]$downgrade))
ROCperf_Z2 <- performance(ROCpred_Z2, 'tpr','fpr')
df_ROC_Z2 <- data.frame(FalsePositive=c(ROCperf_Z2@x.values[[1]]),
TruePositive=c(ROCperf_Z2@y.values[[1]]))
auc_Z2 <- performance(ROCpred_Z2, measure = "auc")
pred_DD2 <- predict(fit_DD2, train, type="response")
#[!is.na(pred)]
ROCpred_DD2 <- prediction(as.numeric(pred_DD2[!is.na(train$downgrade) & !is.na(pred_DD2)]), as.numeric(train[!is.na(train$downgrade) & !is.na(pred_DD2),]$downgrade))
ROCperf_DD2 <- performance(ROCpred_DD2, 'tpr','fpr')
df_ROC_DD2 <- data.frame(FalsePositive=c(ROCperf_DD2@x.values[[1]]),
TruePositive=c(ROCperf_DD2@y.values[[1]]))
ggplot() + geom_line(data=df_ROC_DD2, aes(x=FalsePositive, y=TruePositive, color='DD')) + geom_line(data=df_ROC_Z2, aes(x=FalsePositive, y=TruePositive, color='Z')) + geom_abline(slope=1)

auc_DD2 <- performance(ROCpred_DD2, measure = "auc")
AUCs <- c(auc_Z2@y.values[[1]], auc_DD2@y.values[[1]])
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.6465042 0.5847885
pred_Z2 <- predict(fit_Z2, test, type="response")
ROCpred_Z2 <- prediction(as.numeric(pred_Z2[!is.na(test$downgrade) & !is.na(pred_Z2)]), as.numeric(test[!is.na(test$downgrade) & !is.na(pred_Z2),]$downgrade))
ROCperf_Z2 <- performance(ROCpred_Z2, 'tpr','fpr')
df_ROC_Z2 <- data.frame(FalsePositive=c(ROCperf_Z2@x.values[[1]]),
TruePositive=c(ROCperf_Z2@y.values[[1]]))
auc_Z2 <- performance(ROCpred_Z2, measure = "auc")
pred_DD2 <- predict(fit_DD2, test, type="response")
ROCpred_DD2 <- prediction(as.numeric(pred_DD2[!is.na(test$downgrade) & !is.na(pred_DD2)]), as.numeric(test[!is.na(test$downgrade) & !is.na(pred_DD2),]$downgrade))
ROCperf_DD2 <- performance(ROCpred_DD2, 'tpr','fpr')
df_ROC_DD2 <- data.frame(FalsePositive=c(ROCperf_DD2@x.values[[1]]),
TruePositive=c(ROCperf_DD2@y.values[[1]]))
ggplot() + geom_line(data=df_ROC_DD2, aes(x=FalsePositive, y=TruePositive, color='DD')) + geom_line(data=df_ROC_Z2, aes(x=FalsePositive, y=TruePositive, color='Z')) + geom_abline(slope=1)

auc_DD2 <- performance(ROCpred_DD2, measure = "auc")
AUCs <- c(auc_Z2@y.values[[1]], auc_DD2@y.values[[1]])
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.8134671 0.7420213
fit_comb <- glm(downgrade ~ diff_Z + diff_DD, data=train, family=binomial)
summary(fit_comb)
Call:
glm(formula = downgrade ~ diff_Z + diff_DD, family = binomial,
data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-3.3263 -0.4431 -0.4430 -0.3892 2.7504
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.27217 0.06144 -36.980 < 2e-16 ***
diff_Z -0.71374 0.10709 -6.665 2.65e-11 ***
diff_DD -0.04884 0.04638 -1.053 0.292
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2145.3 on 3277 degrees of freedom
Residual deviance: 2064.7 on 3275 degrees of freedom
AIC: 2070.7
Number of Fisher Scoring iterations: 5
fit_comb %>%
margins::margins() %>%
summary()
pred_comb <- predict(fit_comb, test, type="response")
pred_comb <- ifelse(!is.finite(pred_comb),NA,pred_comb)
ROCpred_comb <- prediction(as.numeric(pred_comb[!is.na(test$downgrade) & !is.na(pred_comb)]), as.numeric(test[!is.na(test$downgrade) & !is.na(pred_comb),]$downgrade))
ROCperf_comb <- performance(ROCpred_comb, 'tpr','fpr')
df_ROC_comb <- data.frame(FalsePositive=c(ROCperf_comb@x.values[[1]]),
TruePositive=c(ROCperf_comb@y.values[[1]]))
auc_comb <- performance(ROCpred_comb, measure = "auc")
ggplot() +
geom_line(data=df_ROC_comb, aes(x=FalsePositive, y=TruePositive, color='Combined')) +
geom_line(data=df_ROC_Z2, aes(x=FalsePositive, y=TruePositive, color='Z')) +
geom_abline(slope=1) +
geom_line(data=df_ROC_DD2, aes(x=FalsePositive, y=TruePositive, color='DD'))

AUCs <- c(auc_comb@y.values[[1]], auc_Z2@y.values[[1]], auc_DD2@y.values[[1]])
names(AUCs) <- c("Combined", "Z", "DD")
AUCs
Combined Z DD
0.8151596 0.8134671 0.7420213
LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gNSINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KTm90ZSB0aGF0IHRoZSBkaXJlY3RvcmllcyB1c2VkIHRvIHN0b3JlIGRhdGEgYXJlIGxpa2VseSBkaWZmZXJlbnQgb24geW91ciBjb21wdXRlciwgYW5kIHN1Y2ggcmVmZXJlbmNlcyB3aWxsIG5lZWQgdG8gYmUgY2hhbmdlZCBiZWZvcmUgdXNpbmcgYW55IHN1Y2ggY29kZS4NCg0KYGBge3IgaGVscGVycywgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpodG1sX2RmIDwtIGZ1bmN0aW9uKHRleHQsIGNvbHM9TlVMTCwgY29sMT1GQUxTRSwgZnVsbD1GKSB7DQogIGlmKCFsZW5ndGgoY29scykpIHsNCiAgICBjb2xzPWNvbG5hbWVzKHRleHQpDQogIH0NCiAgaWYoIWNvbDEpIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpDQogIH0gZWxzZSB7DQogICAga2FibGUodGV4dCwiaHRtbCIsIGNvbC5uYW1lcyA9IGNvbHMsIGFsaWduID0gYygibCIscmVwKCdjJyxsZW5ndGgoY29scyktMSkpKSAlPiUNCiAgICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwiaG92ZXIiKSwgZnVsbF93aWR0aD1mdWxsKSAlPiUNCiAgICAgIGNvbHVtbl9zcGVjKDEsYm9sZD1UKQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShsdWJyaWRhdGUpDQpkZiA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TZXNzaW9uXzUtMS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KZGZfcmF0aW5ncyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TZXNzaW9uXzUtMi5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KZGZfbXZlIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fNS0zLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpkZl9yZiA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TZXNzaW9uXzUtNC5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KZGZfc3RvY2sgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvU2Vzc2lvbl81LTUuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCmBgYA0KDQpgYGB7cn0NCiMgaW5pdGlhbCBjbGVhbmluZw0KIyAxMDAzMzggaXMgYW4gb3V0bGllciBpbiB0aGUgYm9uZHMgZGlzdHJpYnV0aW9uDQpkZiA8LSBkZiAlPiUgZmlsdGVyKGF0ID49IDEsIHJldnQgPj0gMSwgZ3ZrZXkgIT0gMTAwMzM4KQ0KDQojIyBNZXJnZSBpbiBzdG9jayB2YWx1ZQ0KZGYkZGF0ZSA8LSBhcy5EYXRlKGRmJGRhdGFkYXRlKQ0KZGZfbXZlIDwtIGRmX212ZSAlPiUNCiAgbXV0YXRlKGRhdGUgPSBhcy5EYXRlKGRhdGFkYXRlKSwNCiAgICAgICAgIG12ZSA9IGNzaG8gKiBwcmNjX2YpICU+JQ0KICByZW5hbWUoZ3ZrZXk9R1ZLRVkpDQoNCmRmIDwtIGxlZnRfam9pbihkZiwgZGZfbXZlWyxjKCJndmtleSIsImRhdGUiLCJtdmUiKV0pDQoNCmRmIDwtIGRmICU+JQ0KICBncm91cF9ieShndmtleSkgJT4lDQogIG11dGF0ZShiYW5rcnVwdCA9IGlmZWxzZShyb3dfbnVtYmVyKCkgPT0gbigpICYgZGxyc24gPT0gMiAmDQogICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEoZGxyc24pLCAxLCAwKSkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQoNCmBgYHtyfQ0KDQojIENhbGN1bGF0ZSB0aGUgbWVhc3VyZXMgbmVlZGVkDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlKHdjYXBfYXQgPSB3Y2FwIC8gYXQsICAjIHgxDQogICAgICAgICByZV9hdCA9IHJlIC8gYXQsICAjIHgyDQogICAgICAgICBlYml0X2F0ID0gZWJpdCAvIGF0LCAgIyB4Mw0KICAgICAgICAgbXZlX2x0ID0gbXZlIC8gbHQsICAjIHg0DQogICAgICAgICByZXZ0X2F0ID0gcmV2dCAvIGF0KSAgIyB4NQ0KIyBjbGVhbnVwDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGxpc3QofnJlcGxhY2UoLiwgIWlzLmZpbml0ZSguKSwgTkEpKSkNCg0KIyBDYWxjdWxhdGUgdGhlIHNjb3JlDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlKFogPSAxLjIgKiB3Y2FwX2F0ICsgMS40ICogcmVfYXQgKyAzLjMgKiBlYml0X2F0ICsgMC42ICogbXZlX2x0ICsgDQogICAgICAgICAgIDAuOTk5ICogcmV2dF9hdCkNCg0KIyBDYWxjdWxhdGUgZGF0ZSBpbmZvIGZvciBtZXJnaW5nDQpkZiRkYXRlIDwtIGFzLkRhdGUoZGYkZGF0YWRhdGUpDQpkZiR5ZWFyIDwtIHllYXIoZGYkZGF0ZSkNCmRmJG1vbnRoIDwtIG1vbnRoKGRmJGRhdGUpDQpgYGANCg0KYGBge3J9DQojIGRmX3JhdGluZ3MgaGFzIHJhdGluZ3MgZGF0YSBpbiBpdA0KDQojIFJhdGluZ3MsIGluIG9yZGVyIGZyb20gd29yc3QgdG8gYmVzdA0KcmF0aW5ncyA8LSBjKCJEIiwgIkMiLCAiQ0MiLCAiQ0NDLSIsICJDQ0MiLCJDQ0MrIiwgIkItIiwgIkIiLCAiQisiLCAiQkItIiwNCiAgICAgICAgICAgICAiQkIiLCAiQkIrIiwgIkJCQi0iLCAiQkJCIiwgIkJCQisiLCAiQS0iLCAiQSIsICJBKyIsICJBQS0iLCAiQUEiLA0KICAgICAgICAgICAgICJBQSsiLCAiQUFBLSIsICJBQUEiLCAiQUFBKyIpDQojIENvbnZlcnQgc3RyaW5nIHJhdGluZ3MgKHNwbHRpY3JtKSB0byBudW1lcmljIHJhdGluZ3MNCmRmX3JhdGluZ3MkcmF0aW5nIDwtIGZhY3RvcihkZl9yYXRpbmdzJHNwbHRpY3JtLCBsZXZlbHM9cmF0aW5ncywgb3JkZXJlZD1UKQ0KDQpkZl9yYXRpbmdzJGRhdGUgPC0gYXMuRGF0ZShkZl9yYXRpbmdzJGRhdGFkYXRlKQ0KZGZfcmF0aW5ncyR5ZWFyIDwtIHllYXIoZGZfcmF0aW5ncyRkYXRlKQ0KZGZfcmF0aW5ncyRtb250aCA8LSBtb250aChkZl9yYXRpbmdzJGRhdGUpDQoNCiMgTWVyZ2UgdG9nZXRoZXIgZGF0YQ0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9yYXRpbmdzWyxjKCJndmtleSIsICJ5ZWFyIiwgIm1vbnRoIiwgInJhdGluZyIpXSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NH0NCnBsb3QgPC0gZGYgJT4lDQogIGZpbHRlcighaXMubmEoWiksICFpcy5uYShyYXRpbmcpKSAlPiUNCiAgZ3JvdXBfYnkocmF0aW5nKSAlPiUNCiAgbXV0YXRlKG1lYW5fWj1tZWFuKFosbmEucm09VCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QocmF0aW5nLCBtZWFuX1opICU+JQ0KICBnZ3Bsb3QoYWVzKHk9bWVhbl9aLCB4PXJhdGluZykpICsgDQogIGdlb21fY29sKCkgKyANCiAgeWxhYignTWVhbiBBbHRtYW4gWicpICsgeGxhYignQ3JlZGl0IHJhdGluZycpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KZ2dwbG90bHkocGxvdCkNCmBgYA0KDQpgYGB7cn0NCmRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFopLA0KICAgICAgICAgIWlzLm5hKGJhbmtydXB0KSkgJT4lDQogIGdyb3VwX2J5KGJhbmtydXB0KSAlPiUNCiAgbXV0YXRlKG1lYW5fWj1tZWFuKFosbmEucm09VCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoYmFua3J1cHQsIG1lYW5fWikgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD00fQ0KcGxvdCA8LSBkZiAlPiUNCiAgZmlsdGVyKCFpcy5uYShaKSwgIWlzLm5hKHJhdGluZyksIHllYXIgPj0gMjAwMCkgJT4lDQogIGdyb3VwX2J5KHJhdGluZykgJT4lDQogIG11dGF0ZShtZWFuX1o9bWVhbihaLG5hLnJtPVQpKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KHJhdGluZywgbWVhbl9aKSAlPiUNCiAgZ2dwbG90KGFlcyh5PW1lYW5fWiwgeD1yYXRpbmcpKSArIA0KICBnZW9tX2NvbCgpICsgDQogIHlsYWIoJ01lYW4gQWx0bWFuIFonKSArIHhsYWIoJ0NyZWRpdCByYXRpbmcnKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmdncGxvdGx5KHBsb3QpDQpgYGANCg0KYGBge3J9DQpkZiAlPiUNCiAgZmlsdGVyKCFpcy5uYShaKSwNCiAgICAgICAgICFpcy5uYShiYW5rcnVwdCksDQogICAgICAgICB5ZWFyID49IDIwMDApICU+JQ0KICBncm91cF9ieShiYW5rcnVwdCkgJT4lDQogIG11dGF0ZShtZWFuX1o9bWVhbihaLG5hLnJtPVQpKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KGJhbmtydXB0LCBtZWFuX1opICU+JQ0KICBodG1sX2RmKCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GfQ0KZml0X1ogPC0gZ2xtKGJhbmtydXB0IH4gWiwgZGF0YT1kZiwgZmFtaWx5PWJpbm9taWFsKQ0Kc3VtbWFyeShmaXRfWikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GfQ0KbGlicmFyeShST0NSKQ0KZGZaIDwtIGRmICU+JSBmaWx0ZXIoIWlzLm5hKFopLCAhaXMubmEoYmFua3J1cHQpKQ0KcHJlZF9aIDwtIHByZWRpY3QoZml0X1osIGRmWiwgdHlwZT0icmVzcG9uc2UiKQ0KUk9DcHJlZF9aIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkX1opLCBhcy5udW1lcmljKGRmWiRiYW5rcnVwdCkpDQpST0NwZXJmX1ogPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9aLCAndHByJywnZnByJykNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KZGZfUk9DX1ogPC0gZGF0YS5mcmFtZSgNCiAgRlA9YyhST0NwZXJmX1pAeC52YWx1ZXNbWzFdXSksDQogIFRQPWMoUk9DcGVyZl9aQHkudmFsdWVzW1sxXV0pKQ0KZ2dwbG90KGRhdGE9ZGZfUk9DX1osDQogIGFlcyh4PUZQLCB5PVRQKSkgKyBnZW9tX2xpbmUoKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NX0NCnBsb3QoUk9DcGVyZl9aKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGE9ZGZfUk9DX1osIGFlcyh4PUZQLCB5PVRQKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpICsNCiAgeWxhYigiVHJ1ZSBwb3NpdGl2ZSByYXRlIChTZW5zaXRpdml0eSkiKSArIA0KICB4bGFiKCJGYWxzZSBwb3NpdGl2ZSByYXRlICgxIC0gU3BlY2lmaWNpdHkpIikgKw0KICBnZ3RpdGxlKCJST0MgQ3VydmUiKQ0KYGBgDQoNCmBgYHtyfQ0KYXVjX1ogPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9aLCBtZWFzdXJlID0gImF1YyIpDQphdWNfWkB5LnZhbHVlc1tbMV1dDQpgYGANCg0KYGBge3J9DQpzY29yZSA9IDENCm0gPSAwDQpzdGQgPSAxDQoNCmZ1bmNTaGFkZWQgPC0gZnVuY3Rpb24oeCwgbG93ZXJfYm91bmQpIHsNCiAgICB5ID0gZG5vcm0oeCwgbWVhbiA9IG0sIHNkID0gc3RkKQ0KICAgIHlbeCA8IGxvd2VyX2JvdW5kXSA8LSBOQQ0KICAgIHJldHVybih5KQ0KfQ0KDQpnZ3Bsb3QoZGF0YS5mcmFtZSh4ID0gYygtMywgMykpLCBhZXMoeCA9IHgpKSArIA0KICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBhcmdzID0gbGlzdChtZWFuID0gbSwgc2QgPSBzdGQpKSArIA0KICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGZ1bmNTaGFkZWQsIGFyZ3MgPSBsaXN0KGxvd2VyX2JvdW5kID0gc2NvcmUpLCANCiAgICAgICAgICAgICAgICBnZW9tID0gImFyZWEiLCBmaWxsID0gJ2JsYWNrJywgYWxwaGEgPSAuMikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICJTY29yZSIsIGJyZWFrcyA9IHNlcSgtMywgMywgc3RkKSkgKyANCiAgZ2VvbV90ZXh0KGRhdGEgPSBkYXRhLmZyYW1lKHg9YygxLjUpLCB5PWMoMC4wNSkpLCBhZXMoeD14LCB5PXksIGxhYmVsPSJQcm9iKGRlZmF1bHQpIiwgc2l6ZT0zMCkpICsgDQogIGdlb21fbGluZShkYXRhID0gZGF0YS5mcmFtZSh4PWMoMSwxKSwgeT1jKDAsMC40KSksIGFlcyh4PXgseT15KSkgKyANCiAgZ2VvbV90ZXh0KGRhdGEgPSBkYXRhLmZyYW1lKHg9YygxLjMpLCB5PWMoMC40KSksIGFlcyh4PXgsIHk9eSwgbGFiZWw9IkREIiwgc2l6ZT0zMCkpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikNCmBgYA0KDQpgYGB7cn0NCiMgZGZfc3RvY2sgaXMgYW4gYWxyZWFkeSBwcmVwcGVkIGNzdiBmcm9tIENSU1AgZGF0YQ0KZGZfc3RvY2skZGF0ZSA8LSBhcy5EYXRlKGRmX3N0b2NrJGRhdGUpDQpkZiA8LSBsZWZ0X2pvaW4oZGYsIGRmX3N0b2NrWyxjKCJndmtleSIsICJkYXRlIiwgInJldCIsICJyZXQuc2QiKV0pDQpgYGANCg0KYGBge3J9DQoNCmRmX3JmJGRhdGUgPC0gYXMuRGF0ZShkZl9yZiRkYXRlZmYpDQpkZl9yZiR5ZWFyIDwtIHllYXIoZGZfcmYkZGF0ZSkNCmRmX3JmJG1vbnRoIDwtIG1vbnRoKGRmX3JmJGRhdGUpDQoNCmRmIDwtIGxlZnRfam9pbihkZiwgZGZfcmZbLGMoInllYXIiLCAibW9udGgiLCAicmYiKV0pDQoNCmRmIDwtIGRmICU+JQ0KICBtdXRhdGUoREQgPSAobG9nKG12ZSAvIGx0KSArIChyZiAtIChyZXQuc2Qqc3FydCgyNTMpKV4yIC8gMikpIC8NCiAgICAgICAgICAgICAgKHJldC5zZCpzcXJ0KDI1MykpKQ0KIyBDbGVhbiB0aGUgbWVhc3VyZQ0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBsaXN0KH5yZXBsYWNlKC4sICFpcy5maW5pdGUoLiksIE5BKSkpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTR9DQpwbG90IDwtIGRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKEREKSwNCiAgICAgICAgICFpcy5uYShyYXRpbmcpKSAlPiUNCiAgZ3JvdXBfYnkocmF0aW5nKSAlPiUNCiAgbXV0YXRlKG1lYW5fREQ9bWVhbihERCxuYS5ybT1UKSwNCiAgICAgICAgIHByb2JfZGVmYXVsdCA9IHBub3JtKC0xICogbWVhbl9ERCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QocmF0aW5nLCBwcm9iX2RlZmF1bHQpICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cHJvYl9kZWZhdWx0LCB4PXJhdGluZykpICsgDQogIGdlb21fY29sKCkgKyANCiAgeWxhYignUHJvYmFiaWxpdHkgb2YgZGVmYXVsdCcpICsgeGxhYignQ3JlZGl0IHJhdGluZycpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KZ2dwbG90bHkocGxvdCkNCmBgYA0KDQpgYGB7cn0NCmRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKEREKSwNCiAgICAgICAgICFpcy5uYShiYW5rcnVwdCkpICU+JQ0KICBncm91cF9ieShiYW5rcnVwdCkgJT4lDQogIG11dGF0ZShtZWFuX0REPW1lYW4oREQsIG5hLnJtPVQpLA0KICAgICAgICAgcHJvYl9kZWZhdWx0ID0NCiAgICAgICAgICAgcG5vcm0oLTEgKiBtZWFuX0REKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChiYW5rcnVwdCwgbWVhbl9ERCwNCiAgICAgICAgIHByb2JfZGVmYXVsdCkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD00fQ0KcGxvdCA8LSBkZiAlPiUNCiAgZmlsdGVyKCFpcy5uYShERCksDQogICAgICAgICAhaXMubmEocmF0aW5nKSwNCiAgICAgICAgIHllYXIgPj0gMjAwMCkgJT4lDQogIGdyb3VwX2J5KHJhdGluZykgJT4lDQogIG11dGF0ZShtZWFuX0REPW1lYW4oREQsbmEucm09VCksDQogICAgICAgICBwcm9iX2RlZmF1bHQgPSBwbm9ybSgtMSAqIG1lYW5fREQpKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KHJhdGluZywgcHJvYl9kZWZhdWx0KSAlPiUNCiAgZ2dwbG90KGFlcyh5PXByb2JfZGVmYXVsdCwgeD1yYXRpbmcpKSArIA0KICBnZW9tX2NvbCgpICsgDQogIHlsYWIoJ1Byb2JhYmlsaXR5IG9mIGRlZmF1bHQnKSArIHhsYWIoJ0NyZWRpdCByYXRpbmcnKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmdncGxvdGx5KHBsb3QpDQpgYGANCg0KYGBge3J9DQpkZiAlPiUNCiAgZmlsdGVyKCFpcy5uYShERCksDQogICAgICAgICAhaXMubmEoYmFua3J1cHQpLA0KICAgICAgICAgeWVhciA+PSAyMDAwKSAlPiUNCiAgZ3JvdXBfYnkoYmFua3J1cHQpICU+JQ0KICBtdXRhdGUobWVhbl9ERD1tZWFuKERELCBuYS5ybT1UKSwNCiAgICAgICAgIHByb2JfZGVmYXVsdCA9DQogICAgICAgICAgIHBub3JtKC0xICogbWVhbl9ERCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoYmFua3J1cHQsIG1lYW5fREQsDQogICAgICAgICBwcm9iX2RlZmF1bHQpICU+JQ0KICBodG1sX2RmKCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmZpdF9ERCA8LSBnbG0oYmFua3J1cHQgfiBERCwgZGF0YT1kZiwgZmFtaWx5PWJpbm9taWFsKQ0Kc3VtbWFyeShmaXRfREQpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NH0NCmRmREQgPC0gZGYgJT4lIGZpbHRlcighaXMubmEoREQpLCAhaXMubmEoYmFua3J1cHQpKQ0KcHJlZF9ERCA8LSBwcmVkaWN0KGZpdF9ERCwgZGZERCwgdHlwZT0icmVzcG9uc2UiKQ0KUk9DcHJlZF9ERCA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZF9ERCksIGFzLm51bWVyaWMoZGZERCRiYW5rcnVwdCkpDQpST0NwZXJmX0REIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfREQsICd0cHInLCdmcHInKQ0KZGZfUk9DX0REIDwtIGRhdGEuZnJhbWUoRmFsc2VQb3NpdGl2ZT1jKFJPQ3BlcmZfRERAeC52YWx1ZXNbWzFdXSksDQogICAgICAgICAgICAgICAgIFRydWVQb3NpdGl2ZT1jKFJPQ3BlcmZfRERAeS52YWx1ZXNbWzFdXSkpDQpnZ3Bsb3QoKSArDQogIGdlb21fbGluZShkYXRhPWRmX1JPQ19ERCwgYWVzKHg9RmFsc2VQb3NpdGl2ZSwgeT1UcnVlUG9zaXRpdmUsIGNvbG9yPSJERCIpKSArIA0KICBnZW9tX2xpbmUoZGF0YT1kZl9ST0NfWiwgYWVzKHg9RlAsIHk9VFAsIGNvbG9yPSJaIikpICsgDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpDQpgYGANCg0KYGBge3J9DQojQVVDDQphdWNfREQgPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9ERCwgbWVhc3VyZSA9ICJhdWMiKQ0KQVVDcyA8LSBjKGF1Y19aQHkudmFsdWVzW1sxXV0sIGF1Y19EREB5LnZhbHVlc1tbMV1dKQ0KbmFtZXMoQVVDcykgPC0gYygiWiIsICJERCIpDQpBVUNzDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIGNhbGN1bGF0ZSBkb3duZ3JhZGUNCmRmIDwtIGRmICU+JQ0KICBncm91cF9ieShndmtleSkgJT4lDQogIGFycmFuZ2UoZGF0ZSkgJT4lDQogIG11dGF0ZShkb3duZ3JhZGUgPSBpZmVsc2UocmF0aW5nIDwgbGFnKHJhdGluZyksMSwwKSwNCiAgICAgICAgIGRpZmZfWiA9IFogLSBsYWcoWiksDQogICAgICAgICBkaWZmX0REID0gREQgLSBsYWcoREQpKSAlPiUNCiAgdW5ncm91cCgpDQoNCg0KIyB0cmFpbmluZyBzYW1wbGUNCnRyYWluIDwtIGRmICU+JSBmaWx0ZXIoeWVhciA8IDIwMTQsICFpcy5uYShkaWZmX1opLCAhaXMubmEoZGlmZl9ERCksICFpcy5uYShkb3duZ3JhZGUpLA0KICAgICAgICAgICAgICAgICAgICAgICB5ZWFyID4gMTk4NSkNCnRlc3QgPC0gZGYgJT4lIGZpbHRlcih5ZWFyID49IDIwMTQsICFpcy5uYShkaWZmX1opLCAhaXMubmEoZGlmZl9ERCksICFpcy5uYShkb3duZ3JhZGUpKQ0KDQojIGdsbXMNCmZpdF9aMiA8LSBnbG0oZG93bmdyYWRlIH4gZGlmZl9aLCBkYXRhPXRyYWluLCBmYW1pbHk9Ymlub21pYWwpDQpmaXRfREQyIDwtIGdsbShkb3duZ3JhZGUgfiBkaWZmX0RELCBkYXRhPXRyYWluLCBmYW1pbHk9Ymlub21pYWwpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGZpdF9aMikNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkoZml0X0REMikNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KcHJlZF9aMiA8LSBwcmVkaWN0KGZpdF9aMiwgdHJhaW4sIHR5cGU9InJlc3BvbnNlIikNCnByZWRfWjIgPC0gaWZlbHNlKCFpcy5maW5pdGUocHJlZF9aMiksTkEscHJlZF9aMikNCg0KUk9DcHJlZF9aMiA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZF9aMlshaXMubmEodHJhaW4kZG93bmdyYWRlKSAmICFpcy5uYShwcmVkX1oyKV0pLCBhcy5udW1lcmljKHRyYWluWyFpcy5uYSh0cmFpbiRkb3duZ3JhZGUpICYgIWlzLm5hKHByZWRfWjIpLF0kZG93bmdyYWRlKSkNClJPQ3BlcmZfWjIgPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9aMiwgJ3RwcicsJ2ZwcicpDQpkZl9ST0NfWjIgPC0gZGF0YS5mcmFtZShGYWxzZVBvc2l0aXZlPWMoUk9DcGVyZl9aMkB4LnZhbHVlc1tbMV1dKSwNCiAgICAgICAgICAgICAgICAgVHJ1ZVBvc2l0aXZlPWMoUk9DcGVyZl9aMkB5LnZhbHVlc1tbMV1dKSkNCmF1Y19aMiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX1oyLCBtZWFzdXJlID0gImF1YyIpDQoNCg0KcHJlZF9ERDIgPC0gcHJlZGljdChmaXRfREQyLCB0cmFpbiwgdHlwZT0icmVzcG9uc2UiKQ0KI1shaXMubmEocHJlZCldDQpST0NwcmVkX0REMiA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZF9ERDJbIWlzLm5hKHRyYWluJGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9ERDIpXSksIGFzLm51bWVyaWModHJhaW5bIWlzLm5hKHRyYWluJGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9ERDIpLF0kZG93bmdyYWRlKSkNClJPQ3BlcmZfREQyIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfREQyLCAndHByJywnZnByJykNCmRmX1JPQ19ERDIgPC0gZGF0YS5mcmFtZShGYWxzZVBvc2l0aXZlPWMoUk9DcGVyZl9ERDJAeC52YWx1ZXNbWzFdXSksDQogICAgICAgICAgICAgICAgIFRydWVQb3NpdGl2ZT1jKFJPQ3BlcmZfREQyQHkudmFsdWVzW1sxXV0pKQ0KZ2dwbG90KCkgKyBnZW9tX2xpbmUoZGF0YT1kZl9ST0NfREQyLCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9J0REJykpICsgZ2VvbV9saW5lKGRhdGE9ZGZfUk9DX1oyLCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9J1onKSkgKyBnZW9tX2FibGluZShzbG9wZT0xKQ0KYXVjX0REMiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX0REMiwgbWVhc3VyZSA9ICJhdWMiKQ0KQVVDcyA8LSBjKGF1Y19aMkB5LnZhbHVlc1tbMV1dLCBhdWNfREQyQHkudmFsdWVzW1sxXV0pDQpuYW1lcyhBVUNzKSA8LSBjKCJaIiwgIkREIikNCkFVQ3MNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KcHJlZF9aMiA8LSBwcmVkaWN0KGZpdF9aMiwgdGVzdCwgdHlwZT0icmVzcG9uc2UiKQ0KUk9DcHJlZF9aMiA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZF9aMlshaXMubmEodGVzdCRkb3duZ3JhZGUpICYgIWlzLm5hKHByZWRfWjIpXSksIGFzLm51bWVyaWModGVzdFshaXMubmEodGVzdCRkb3duZ3JhZGUpICYgIWlzLm5hKHByZWRfWjIpLF0kZG93bmdyYWRlKSkNClJPQ3BlcmZfWjIgPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9aMiwgJ3RwcicsJ2ZwcicpDQpkZl9ST0NfWjIgPC0gZGF0YS5mcmFtZShGYWxzZVBvc2l0aXZlPWMoUk9DcGVyZl9aMkB4LnZhbHVlc1tbMV1dKSwNCiAgICAgICAgICAgICAgICAgVHJ1ZVBvc2l0aXZlPWMoUk9DcGVyZl9aMkB5LnZhbHVlc1tbMV1dKSkNCmF1Y19aMiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX1oyLCBtZWFzdXJlID0gImF1YyIpDQoNCnByZWRfREQyIDwtIHByZWRpY3QoZml0X0REMiwgdGVzdCwgdHlwZT0icmVzcG9uc2UiKQ0KUk9DcHJlZF9ERDIgPC0gcHJlZGljdGlvbihhcy5udW1lcmljKHByZWRfREQyWyFpcy5uYSh0ZXN0JGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9ERDIpXSksIGFzLm51bWVyaWModGVzdFshaXMubmEodGVzdCRkb3duZ3JhZGUpICYgIWlzLm5hKHByZWRfREQyKSxdJGRvd25ncmFkZSkpDQpST0NwZXJmX0REMiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX0REMiwgJ3RwcicsJ2ZwcicpDQpkZl9ST0NfREQyIDwtIGRhdGEuZnJhbWUoRmFsc2VQb3NpdGl2ZT1jKFJPQ3BlcmZfREQyQHgudmFsdWVzW1sxXV0pLA0KICAgICAgICAgICAgICAgICBUcnVlUG9zaXRpdmU9YyhST0NwZXJmX0REMkB5LnZhbHVlc1tbMV1dKSkNCmdncGxvdCgpICsgZ2VvbV9saW5lKGRhdGE9ZGZfUk9DX0REMiwgYWVzKHg9RmFsc2VQb3NpdGl2ZSwgeT1UcnVlUG9zaXRpdmUsIGNvbG9yPSdERCcpKSArIGdlb21fbGluZShkYXRhPWRmX1JPQ19aMiwgYWVzKHg9RmFsc2VQb3NpdGl2ZSwgeT1UcnVlUG9zaXRpdmUsIGNvbG9yPSdaJykpICsgZ2VvbV9hYmxpbmUoc2xvcGU9MSkNCmF1Y19ERDIgPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9ERDIsIG1lYXN1cmUgPSAiYXVjIikNCkFVQ3MgPC0gYyhhdWNfWjJAeS52YWx1ZXNbWzFdXSwgYXVjX0REMkB5LnZhbHVlc1tbMV1dKQ0KbmFtZXMoQVVDcykgPC0gYygiWiIsICJERCIpDQpBVUNzDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9Rn0NCmZpdF9jb21iIDwtIGdsbShkb3duZ3JhZGUgfiBkaWZmX1ogKyBkaWZmX0RELCBkYXRhPXRyYWluLCBmYW1pbHk9Ymlub21pYWwpDQpzdW1tYXJ5KGZpdF9jb21iKQ0KYGBgDQoNCmBgYHtyfQ0KZml0X2NvbWIgJT4lDQogIG1hcmdpbnM6Om1hcmdpbnMoKSAlPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NX0NCnByZWRfY29tYiA8LSBwcmVkaWN0KGZpdF9jb21iLCB0ZXN0LCB0eXBlPSJyZXNwb25zZSIpDQpwcmVkX2NvbWIgPC0gaWZlbHNlKCFpcy5maW5pdGUocHJlZF9jb21iKSxOQSxwcmVkX2NvbWIpDQoNClJPQ3ByZWRfY29tYiA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZF9jb21iWyFpcy5uYSh0ZXN0JGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9jb21iKV0pLCBhcy5udW1lcmljKHRlc3RbIWlzLm5hKHRlc3QkZG93bmdyYWRlKSAmICFpcy5uYShwcmVkX2NvbWIpLF0kZG93bmdyYWRlKSkNClJPQ3BlcmZfY29tYiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX2NvbWIsICd0cHInLCdmcHInKQ0KZGZfUk9DX2NvbWIgPC0gZGF0YS5mcmFtZShGYWxzZVBvc2l0aXZlPWMoUk9DcGVyZl9jb21iQHgudmFsdWVzW1sxXV0pLA0KICAgICAgICAgICAgICAgICAgIFRydWVQb3NpdGl2ZT1jKFJPQ3BlcmZfY29tYkB5LnZhbHVlc1tbMV1dKSkNCmF1Y19jb21iIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfY29tYiwgbWVhc3VyZSA9ICJhdWMiKQ0KDQpnZ3Bsb3QoKSArIA0KICBnZW9tX2xpbmUoZGF0YT1kZl9ST0NfY29tYiwgYWVzKHg9RmFsc2VQb3NpdGl2ZSwgeT1UcnVlUG9zaXRpdmUsIGNvbG9yPSdDb21iaW5lZCcpKSArDQogIGdlb21fbGluZShkYXRhPWRmX1JPQ19aMiwgYWVzKHg9RmFsc2VQb3NpdGl2ZSwgeT1UcnVlUG9zaXRpdmUsIGNvbG9yPSdaJykpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKyANCiAgZ2VvbV9saW5lKGRhdGE9ZGZfUk9DX0REMiwgYWVzKHg9RmFsc2VQb3NpdGl2ZSwgeT1UcnVlUG9zaXRpdmUsIGNvbG9yPSdERCcpKQ0KDQpBVUNzIDwtIGMoYXVjX2NvbWJAeS52YWx1ZXNbWzFdXSwgYXVjX1oyQHkudmFsdWVzW1sxXV0sIGF1Y19ERDJAeS52YWx1ZXNbWzFdXSkNCm5hbWVzKEFVQ3MpIDwtIGMoIkNvbWJpbmVkIiwgIloiLCAiREQiKQ0KQVVDcw0KYGBgDQoNCg==