Artificial intelligence and machine learning in financial services

Credit rating plays an important role in profit optimization and sustainable development of banks in particular as well as other financial institutions. Now, the Machine Learning approach has proven to be superior in terms of accuracy as well as reliability compared to some traditional classification models. The events of the financial crisis led to the collapse of a series of financial institutions in general and banks, in particular, have awakened these organizations to more emphasis on the role of credit appraisals in their operations. Most of the banks’ profits come from credit and loan activities. Credit granting is one of the activities that generate a large proportion of revenue and profit for the bank but also implies a lot of risks [Zakrzewska, 2007]. The main risk of a bank is the clients are not able to repay the loans they have granted. On the other hand, the decision whether or not to provide a loan to the client often depends on the qualifications and experience of the credit assessor [Thomas, 2000].

In addition, the basis for granting credit to a customer is based on a number of criteria that some of them are very difficult to measure, or difficult to measure accurately. For example, the 5 standard criteria for credit granting is based on bank assessments of status, capacity, capital, collateral, and borrower’s conditions [Abrahams & Zhang, 2008]. Clearly, some criteria, such as the status and capacity of a borrower, are difficult to assess and may, therefore, lead to errors in lending decisions. In addition, the credit rating approach based on these 5 standard criteria is costly and there might occur inconsistency on the decision between different credit assessors for the same loan application. Due to these constraints, banks, as well as financial institutions, need to use reliable, objective and low cost crediting and credit ratings to help them decide whether to grant or not. Credits for loan applications [Akhavein, Frame, & White, 2005; Chye, Chin, & Peng, 2004]. Moreover, according to Thomas and CTG (2002)], banks need a credit rating that meets the following requirements: (1) cheap and easy to operate, (2) fast and stable, (3) make consistent decisions based on unbiased information independent of subjective feelings and emotions, and (4) the effectiveness of the credit rating system can be easily check and adjust at any time to timely adjust with changes in policies or conditions of the economy.

For credit classification, the traditional approach is based on pure statistical methods such as multivariate linear regression [Meyer & Pifer, 1970], discriminant analysis [Altman, 1968, Banasik, Crook, & Thomas, 2003], and logistic regression [Desai, Crook, & Overstreet, 1996; Dimitras, Zanakis, & Zopounidis, 1996; Elliott & Filinkov, 2008; Lee, Chiu, Lu, & Chen, 2002]. However, the requirements of the Basel Committee on Banking Supervision issued in 2004 require banks and financial institutions to use credit classification models which are more reliable in order to improve the efficiency of capital allocation. In order to meet these demands, in recent years, there have been some new models of credit classification based on machine learning and artificial intelligence (AL). Unlike previous approaches, these new methods do not give any strict assumptions as to the need for statistical approaches. Instead, these new approaches attempt to exploit and provide the knowledge, the output information based only on inputs that are observations, information from the past. With the credit classification problem, some machine learning models such as Artificial Neural Network (ANN). Support Vector Machines (SVMs), K Nearest Neighbors(KNN) , Random Forest (RF), Decision Tree (DT), has proved to be superior in terms of accuracy as well as reliability compared to some traditional classification models [Chi et al., 2004, Huang et al., & Wang, 2007; Ince & Aktan, 2009; Martens et al. ., 2010].

Methodology

The cost of misclassifying a bad record into a good record is much higher than misclassifying a good record into bad records irrespective of what classification model to use. Rather than using Accuracy as the main criterion for selecting and evaluating models, another better way is to use the “profit criterion”, as known as the economic consequences of using the model, as the selection criterion in this article. Specifically, this approach is described in detail as below:

First , using 4 Machine Learning models/approaches for the problem of classification. For each of the classification thresholds examine the variability of the three criteria for evaluating the quality of the classification model:

  • Bad-Bad Prediction Rate,

  • Good - Good Prediction Rate,

  • Accuracy Rate (Accuracy Rate).

Next, assessing the quality of classification of each approach to the two groups of loan applications (bad and good groups) as well as the general accuracy level of classification models when the threshold changes:

  • Use the Monte Carlo simulation method (1000 times).

  • Interest rate (After deducting the costs like the staff., etc.) is 5% p.a.

  • Distribution of the loan amount (for loan approval records based on bank lending history data).

The dataset

The actual dataset hmeq.csv from “CREDIT RISK ANALYTICS MEASUREMENT TECHNIQUES, APPLICATIONS, and EXAMPLES in SAS” are been using in this article. This dataset can be downloaded directly from the link: http://www.creditriskanalytics.net/datasets-private.html

The data set HMEQ reports characteristics and delinquency information for 5,960 home equity loans. A home equity loan is a loan where the obligor uses the equity of his or her home as the underlying collateral. The data set has the following characteristics:

  • BAD: 1 = applicant defaulted on loan or seriously delinquent; 0 = applicant paid loan
  • LOAN: Amount of the loan request
  • MORTDUE: Amount due on existing mortgage
  • VALUE: Value of current property
  • REASON: DebtCon = debt consolidation; HomeImp = home improvement
  • JOB: Occupational categories
  • YOJ: Years at present job
  • DEROG: Number of major derogatory reports
  • DELINQ: Number of delinquent credit lines
  • CLAGE: Age of oldest credit line in months
  • NINQ: Number of recent credit inquiries
  • CLNO: Number of credit lines
  • DEBTINC: Debt-to-income ratio

Modelling

Here are the R codes and the experimental results obtained from the hmeq.csv dataset:

Pre-processing data

# Loading data


library(tidyverse)
library(magrittr)
hmeq <- read.csv("http://www.creditriskanalytics.net/uploads/1/9/5/1/19511601/hmeq.csv", sep = ",", header = TRUE)
head(hmeq)
##   BAD LOAN MORTDUE  VALUE  REASON    JOB  YOJ DEROG DELINQ     CLAGE NINQ
## 1   1 1100   25860  39025 HomeImp  Other 10.5     0      0  94.36667    1
## 2   1 1300   70053  68400 HomeImp  Other  7.0     0      2 121.83333    0
## 3   1 1500   13500  16700 HomeImp  Other  4.0     0      0 149.46667    1
## 4   1 1500      NA     NA                  NA    NA     NA        NA   NA
## 5   0 1700   97800 112000 HomeImp Office  3.0     0      0  93.33333    0
## 6   1 1700   30548  40320 HomeImp  Other  9.0     0      0 101.46600    1
##   CLNO  DEBTINC
## 1    9       NA
## 2   14       NA
## 3   10       NA
## 4   NA       NA
## 5   14       NA
## 6    8 37.11361

Some data processing functions for filling and re-labeling missing data

## 1
replace_na_mean <- function(x){
  mean <- mean(x, na.rm = TRUE)
  x[is.na(x)] <- mean
  return(x)
}



## 2
name_job <- function(x){
  x %<>% as.character()
  ELSE <- TRUE
  job_name <- c("Mgr", "Office", "Other", "ProfExe", "Sales", "Self")
  case_when(!x %in% job_name ~ "Other", 
            ELSE ~ x) 

} 



## 3
name_reason <- function(x){
  ELSE <- TRUE
  x %<>% as.character()
  case_when(!x  %in%  c("DebtCon", "HomeImp") ~ "Unknown",
            ELSE ~ x)
    
}



## 4
label_rename <- function(x){
  case_when(x==1 ~ "BAD",
            x==0 ~ "GOOD")
}




## 5
my_scale01 <- function(x){
  (x-min(x))/(max(x) - min(x))
} ## Rescaled the data/ normalization
# Applied these functions



## Final data for slitting 
df <- hmeq %>% 
  mutate_if(is.numeric, replace_na_mean) %>% 
   mutate_at("REASON", name_reason) %>% 
  mutate_at("JOB", name_job) %>% 
  mutate(BAD = label_rename(BAD)) %>% 
  mutate_if(is.character, as.factor) %>% 
  mutate_if(is.numeric, my_scale01)


head(df)
##    BAD        LOAN    MORTDUE      VALUE  REASON    JOB        YOJ
## 1  BAD 0.000000000 0.05986862 0.03659001 HomeImp  Other 0.25609756
## 2  BAD 0.002252252 0.17104962 0.07123406 HomeImp  Other 0.17073171
## 3  BAD 0.004504505 0.02877327 0.01026054 HomeImp  Other 0.09756098
## 4  BAD 0.004504505 0.18037777 0.11059683 Unknown  Other 0.21761630
## 5 GOOD 0.006756757 0.24085568 0.12265467 HomeImp Office 0.07317073
## 6  BAD 0.006756757 0.07166272 0.03811730 HomeImp  Other 0.21951220
##        DEROG     DELINQ      CLAGE       NINQ      CLNO   DEBTINC
## 1 0.00000000 0.00000000 0.08077723 0.05882353 0.1267606 0.1639913
## 2 0.00000000 0.13333333 0.10428851 0.00000000 0.1971831 0.1639913
## 3 0.00000000 0.00000000 0.12794245 0.05882353 0.1408451 0.1639913
## 4 0.02545697 0.02996283 0.15387871 0.06976794 0.2999450 0.1639913
## 5 0.00000000 0.00000000 0.07989270 0.00000000 0.1971831 0.1639913
## 6 0.00000000 0.00000000 0.08685421 0.05882353 0.1126761 0.1804307

Train and test datasets

# Prepare train and test datasets 
library(caret)
set.seed(1)
id <- createDataPartition(y = df$BAD, p = 0.5, list = FALSE)
train <- df[id,]
test <- df[id,]




# Set up parameterization and cross-validation:
set.seed(1)
trainControl <- trainControl(method = "repeatedcv",
                             number = 5,
                             repeats = 5,
                             classProbs = TRUE,
                             allowParallel = TRUE,
                             summaryFunction = multiClassSummary) 

Parallel computing

# Set up parallel computing mode

library(doParallel)
n_cores <- detectCores()

registerDoParallel(cores = n_cores - 1)
# Write the average calculation functions for 
# the classification criteria of the model 
# with the selection of 1000 observation patterns from testing data 100 times.

Functional programming setup for evaluation

eval_fun1 <- function(threshold, model_selected){
  my_df <- data.frame()
  for (i in 1:100){
    set.seed(1) ## set.seed after calling my_df to make the samples unduplicated
    
    id <- createDataPartition(y = test$BAD, p = 1000/nrow(test), list = FALSE)
   
     test_df <- test[id,]
    
     predict <- predict(model_selected, test_df, type = "prob") %>%
      pull(BAD)
    
     predict <- case_when(predict >= threshold ~ "BAD",
                          predict < threshold ~ "GOOD")
     
     cm <- confusionMatrix(test_df$BAD, predict %>%  as.factor())
     ## the confusion matrix is in 2 by 2
     
     bg_gg <- cm$table %>% 
       as.vector() %>% 
       matrix(ncol = 4) %>% 
       as.data.frame()
     # convert it into matrix of 1 by 4
     
    names(bg_gg) <- c("BB", "GB", "BG", "GG")
    
    result <- c(cm$overall, cm$byClass)
    names <- result %>% as.data.frame() %>% row.names()
    
    result %>% 
      as.vector() %>% 
      matrix(ncol = 18) %>% 
      as.data.frame() -> all_df 
    
    names(all_df) <- names
    all_df <- bind_cols(all_df, bg_gg)
    my_df <- bind_rows(my_df, all_df)
     }
  return(my_df)
}
## Write a function calculating the classification 
# of the model based on a pre-selected threshold range:

my_results_from_thres_range <- function(low_thres, up_thres, by, model_selected) {
  my_range <- seq(low_thres, up_thres, by = by)
  n <- length(my_range)
  all_df <- data.frame()
  
  for (i in 1:n) {
    df <- eval_fun1(my_range[i], model_selected = model_selected)
    df %<>% mutate(Threshold = paste("T", my_range[i]))
    all_df <- bind_rows(all_df, df)
  }
  return(all_df)
  
}

Train a variety of Machine Learning models

Random Forest

set.seed(1)
my_rf <- train(BAD ~.,
               data = train,
               method = "rf",
               metric = "AUC", 
               trControl = trainControl,
               tuneLength = 5
               
               )

Support Vector Machine

set.seed(1)
my_svm <- train(BAD ~.,
                data = train,
                method = "svmRadial",
                metric = "AUC",
                trControl = trainControl,
                tuneLength = 5)

Gradient Boosting Machine

set.seed(1)
my_gbm <- train(BAD ~.,
                data = train,
                method = "gbm",
                metric = "AUC",
                trControl = trainControl,
                tuneLength = 5)
## Iter   TrainDeviance   ValidDeviance   StepSize   Improve
##      1        0.9206             nan     0.1000    0.0376
##      2        0.8851             nan     0.1000    0.0177
##      3        0.8565             nan     0.1000    0.0124
##      4        0.8322             nan     0.1000    0.0106
##      5        0.7978             nan     0.1000    0.0164
##      6        0.7695             nan     0.1000    0.0132
##      7        0.7533             nan     0.1000    0.0068
##      8        0.7270             nan     0.1000    0.0121
##      9        0.7133             nan     0.1000    0.0054
##     10        0.6945             nan     0.1000    0.0088
##     20        0.6009             nan     0.1000    0.0034
##     40        0.5164             nan     0.1000    0.0001
##     60        0.4707             nan     0.1000   -0.0001
##     80        0.4340             nan     0.1000    0.0001
##    100        0.4048             nan     0.1000    0.0001
##    120        0.3782             nan     0.1000    0.0003
##    140        0.3566             nan     0.1000   -0.0001
##    160        0.3337             nan     0.1000   -0.0001
##    180        0.3144             nan     0.1000   -0.0002
##    200        0.2985             nan     0.1000    0.0001
##    220        0.2841             nan     0.1000   -0.0006
##    240        0.2699             nan     0.1000   -0.0000
##    250        0.2610             nan     0.1000   -0.0000

Boosted Classification Trees - ADA Boost

set.seed(1)
my_ada <- train(BAD ~.,
                data = train,
                method = "ada",
                metric = "AUC",
                trControl = trainControl,
                tuneLength = 5)  

Results for those models after testing

# Getting results fro the thresholds from 0.1 - 0.8

my_results_from_thres_range(0.1, 0.8, 0.1, my_rf) ->> rf_results

my_results_from_thres_range(0.1, 0.8, 0.1, my_svm) ->> svm_results

my_results_from_thres_range(0.1, 0.8, 0.1, my_gbm) ->> gbm_results

my_results_from_thres_range(0.1, 0.8, 0.1, my_ada) ->> ada_results




# Summary of results

total_df_results <- bind_rows(rf_results %>% mutate(Model = "RF"), 
                              svm_results %>% mutate(Model = "SVM"), 
                              gbm_results %>% mutate(Model = "GBM"), 
                              ada_results %>% mutate(Model = "ADA") )


names(total_df_results) <- names(total_df_results) %>% str_replace_all(" ", "")


## Median calculus

compared_results <- total_df_results %>% 
  group_by(Threshold, Model) %>% 
  summarise_each(funs(median), Accuracy, NegPredValue, PosPredValue) %>% 
  ungroup() 

head(compared_results)
## # A tibble: 6 x 5
##   Threshold Model Accuracy NegPredValue PosPredValue
##   <chr>     <chr>    <dbl>        <dbl>        <dbl>
## 1 T 0.1     ADA      0.944        0.930        1    
## 2 T 0.1     GBM      0.886        0.859        0.995
## 3 T 0.1     RF       0.925        0.906        1    
## 4 T 0.1     SVM      0.687        0.633        0.905
## 5 T 0.2     ADA      0.954        0.954        0.955
## 6 T 0.2     GBM      0.938        0.934        0.955

Write the threshold extraction function:

get_number <- function(x) {
  x %>% 
    str_replace_all("[^0-9]", "") %>% 
    as.numeric() ->> y
  return(y*0.1)
}

Visualization

Good - Good Prediction

theme_set(theme_minimal())

compared_results  %>%  
  mutate(Threshold = get_number(Threshold)) %>% 
  ggplot(aes(Threshold, NegPredValue, color = Model)) +
  geom_line()+
  geom_point()+
  scale_y_continuous(labels = scales::percent)+
  scale_x_continuous(breaks = seq(0.1, 0.8, 0.1))+
  labs(y= "Good - Good Predicton Rate",
       title = "Figure 1 : Good - Good prediction rate by threshold"
       )

Bad - Bad Prediction

compared_results  %>%  
  mutate(Threshold = get_number(Threshold)) %>% 
  ggplot(aes(Threshold, PosPredValue, color = Model)) +
  geom_line()+
  geom_point()+
  scale_y_continuous(labels = scales::percent)+
  scale_x_continuous(breaks = seq(0.1, 0.8, 0.1))+
  labs(y= "Bad - Bad Predicton Rate",
       title = " Figure 2 : Bad - Bad prediction rate by threshold"
  )

Total Accuracy Rate

compared_results  %>%  
  mutate(Threshold = get_number(Threshold)) %>% 
  ggplot(aes(Threshold, Accuracy, color = Model)) +
  geom_line()+
  geom_point()+
  scale_y_continuous(labels = scales::percent)+
  scale_x_continuous(breaks = seq(0.1, 0.8, 0.1))+
  labs(y= " Accuracy Rate",
       title = "Figure 3 : Accuracy rate by threshold"
  )

Because The cost of misclassifying a bad record into a good record is much higher than misclassifying a good record into bad records irrespective of what classification model are used. Therefore, accuarcy is not always the best criteria to choose. Threshold = 0.5 is not appropriate in all situations. That means, changing threshold for a specific model is really life-changing. In this case, I go for the threshold in the range of (0.2,0.4).

New approach - Estimation of profit is based on simulation when threshold changes

Getting GG - BG predictions

# Random Forest

rf_results %>% 
  group_by(Threshold) %>% 
  summarise_each(funs(sum), GG, BG) ->> gg_bg_RF_Model


# Support Vector Machine

svm_results %>% 
  group_by(Threshold) %>% 
  summarise_each(funs(sum), GG, BG) ->> gg_bg_SVM_Model

  
# Gradient Boosting Machine

gbm_results %>% 
  group_by(Threshold) %>% 
  summarise_each(funs(sum), GG, BG) ->> gg_bg_gbm_Model


# Boosted Classification Trees - ADA Boost

ada_results %>% 
  group_by(Threshold) %>% 
  summarise_each(funs(sum), GG, BG) ->> gg_bg_ADA_Model



## Write the profit simulation function with 1000 simulations:


profit_simu <- function(data_from_model, row, rate) {
  prof <- c()
  for (j in 1:1000) {
    
    good_loan <- data_from_model[row, 2] %>% as.numeric()
    bad_loan <- data_from_model[row, 3] %>% as.numeric()
    
    amount_of_good_loan <- sample(hmeq$LOAN, good_loan, replace = TRUE)
    amount_of_bad_loan <- sample(hmeq$LOAN, bad_loan, replace = TRUE)
    
    return <- sum(rate*amount_of_good_loan) - sum(amount_of_bad_loan)
    prof <- c(prof, return)
  }
  
  data.frame(Profit = prof, Threshold = get_number(data_from_model[row, 1] %>% as.vector())) %>% 
    return()
  
}

Profit simulation

# Profit Simulation by Random Forest

rf_profit <- bind_rows(profit_simu(gg_bg_RF_Model, 1, 0.05), 
                       profit_simu(gg_bg_RF_Model, 2, 0.05), 
                       profit_simu(gg_bg_RF_Model, 3, 0.05), 
                       profit_simu(gg_bg_RF_Model, 4, 0.05), 
                       profit_simu(gg_bg_RF_Model, 5, 0.05), 
                       profit_simu(gg_bg_RF_Model, 6, 0.05), 
                       profit_simu(gg_bg_RF_Model, 7, 0.05), 
                       profit_simu(gg_bg_RF_Model, 8, 0.05 ))


# Profit Simulation by Support Vector Machine 

svm_profit <- bind_rows(profit_simu(gg_bg_SVM_Model, 1, 0.05), 
                        profit_simu(gg_bg_SVM_Model, 2, 0.05), 
                        profit_simu(gg_bg_SVM_Model, 3, 0.05), 
                        profit_simu(gg_bg_SVM_Model, 4, 0.05), 
                        profit_simu(gg_bg_SVM_Model, 5, 0.05), 
                        profit_simu(gg_bg_SVM_Model, 6, 0.05), 
                        profit_simu(gg_bg_SVM_Model, 7, 0.05), 
                        profit_simu(gg_bg_SVM_Model, 8, 0.05))


# Profit Simulation by Gradient Boosting Machine

gbm_profit <- bind_rows(profit_simu(gg_bg_gbm_Model, 1, 0.05), 
                        profit_simu(gg_bg_gbm_Model, 2, 0.05), 
                        profit_simu(gg_bg_gbm_Model, 3, 0.05), 
                        profit_simu(gg_bg_gbm_Model, 4, 0.05), 
                        profit_simu(gg_bg_gbm_Model, 5, 0.05), 
                        profit_simu(gg_bg_gbm_Model, 6, 0.05), 
                        profit_simu(gg_bg_gbm_Model, 7, 0.05), 
                        profit_simu(gg_bg_gbm_Model, 8, 0.05))


# Profit Simulation by ADA

ada_profit <- bind_rows(profit_simu(gg_bg_ADA_Model, 1, 0.05), 
                        profit_simu(gg_bg_ADA_Model, 2, 0.05), 
                        profit_simu(gg_bg_ADA_Model, 3, 0.05), 
                        profit_simu(gg_bg_ADA_Model, 4, 0.05), 
                        profit_simu(gg_bg_ADA_Model, 5, 0.05), 
                        profit_simu(gg_bg_ADA_Model, 6, 0.05), 
                        profit_simu(gg_bg_ADA_Model, 7, 0.05), 
                        profit_simu(gg_bg_ADA_Model, 8, 0.05))

Profits comparison

profit_compare_models <- bind_rows(rf_profit %>% mutate(Model = "RF"), 
                                   svm_profit %>% mutate(Model = "SVM"), 
                                   gbm_profit %>% mutate(Model = "GBM"), 
                                   ada_profit %>% mutate(Model = "ADA"))
# Visualization

profit_compare_models %>% 
  mutate(Profit = Profit/1000000) %>% 
  group_by(Threshold, Model) %>% 
  summarise_each(funs(mean), Profit) %>% 
  ggplot(aes(Threshold, Profit, color = Model)) +
  geom_line()+
  geom_point()+
  scale_x_continuous(breaks = seq(0.1, 0.8, 0.1)) +
  labs( x= NULL, y = NULL, 
        title = "Figure 4: Simulated Profits from 4 Machine Learning Models\nbased on Monte Carlo Simulation by Threshold" )

# Summary

profit_compare_models %>% 
  filter(Threshold == 0.2) %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, min, max, sd), Profit) %>% 
  arrange(-mean) %>% 
  knitr::kable()
Model mean median min max sd
RF 73595603 73597263 73123300 74025170 159572.8
ADA 54332844 54345873 52982400 55439210 382542.5
GBM 52851104 52846643 51728315 54145680 364493.5
SVM 17494129 17524835 15243285 19379025 614479.9

Can refining parameters for the Random Forest improve profitability for banks?

RF performance when mtry changes

## Set refining grid:

my_grid1 <- expand.grid(mtry = 1:10)


# # RF tuned: 
set.seed(1)
my_rf_tuned <- train(BAD ~., 
                      data = train, 
                      method = "rf", 
                      metric = "AUC", 
                      trControl = trainControl, 
                      tuneGrid = my_grid1)


# Write function visualizes some criteria for evaluating the quality of
# model RF classification when mtry changes:

my_plot <- function(model) {
  theme_set(theme_minimal())
  u <- model$results %>% 
    select(mtry, AUC, Accuracy, Kappa, Sensitivity, Specificity, Precision) %>% 
    gather(a, b, -mtry) 
  
  u %>% 
    ggplot(aes(mtry, b)) + 
    geom_line() + 
    geom_point() + 
    facet_wrap(~ a, scales = "free") + 
    labs(x = "Number of mtry", y = NULL, 
         title = "The Relationship between Model Performance and mtry")
  
}

# Visualisaton:

my_rf_tuned %>% 
  my_plot() + 
  scale_x_continuous(breaks = seq(1, 10, 1))

compare the model without tuning and with tuning

my_results_from_thres_range(0.1, 0.8, 0.1, my_rf_tuned) ->> rf_results_tuned

rf_results_tuned %>% 
  group_by(Threshold) %>% 
  summarise_each(funs(sum), GG, BG) ->> gg_bg_RF_Model_tuned


rf_profit_tuned <- bind_rows(profit_simu(gg_bg_RF_Model_tuned, 1, 0.1), 
                              profit_simu(gg_bg_RF_Model_tuned, 2, 0.1), 
                              profit_simu(gg_bg_RF_Model_tuned, 3, 0.1), 
                              profit_simu(gg_bg_RF_Model_tuned, 4, 0.1), 
                              profit_simu(gg_bg_RF_Model_tuned, 5, 0.1), 
                              profit_simu(gg_bg_RF_Model_tuned, 6, 0.1), 
                              profit_simu(gg_bg_RF_Model_tuned, 7, 0.1), 
                              profit_simu(gg_bg_RF_Model_tuned, 8, 0.1))

two_rf_model <- bind_rows(profit_compare_models %>% filter(Model == "RF"), 
                          rf_profit_tuned %>% mutate(Model = "RF tuned"))

two_rf_model %>% 
  mutate(Profit = Profit / 1000000) %>% 
  group_by(Threshold, Model) %>% 
  summarise_each(funs(mean), Profit) %>% 
  ggplot(aes(Threshold, Profit, color = Model)) + 
  geom_line() + 
  geom_point() + 
  scale_x_continuous(breaks = seq(0.1, 0.8, 0.1)) + 
  labs(x = NULL, y = NULL, 
       title = "Figure 5: Simulated Profit from Dafault and tuned Random Forest\nbased on Monte Carlo Simulation by Threshold", 
       subtitle = "Data Used: http://www.creditriskanalytics.net/datasets-private.html")

Results

  • The profit margin of the tuned RF model is higher at any threshold point.

  • There is always a tradeoff between the accuracy of good records and bad records (Figure 1 and Figure 2).

  • At the level of classification that the highest accuracy is, it is the classification threshold that generates non-optimal returns for the bank.

  • When the classification threshold increases, the profitability of using the classification model decreases.

  • At the default threshold of 0.5 - This is also the default threshold for most commercial software such as SPSS, Stata, and Eviews, the economic consequences (Monte Carlo-based average profitability) of using the classification model are lousy.

  • For the purpose of maximizing profit and using RF without tuning, Random Forest should be used, and the usage threshold is 0.2. With tuned RF the best threshold would be 0.4.

LS0tDQp0aXRsZTogIlByb2ZpdCBNYXhpbWl6YXRpb24gZm9yIGJhbmtzIg0KYXV0aG9yOiAiTWFpIFRoaSBOZ3V5ZW4iDQpkYXRlOiAiMzAgTm92ZW1iZXIgMjAxOCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgICMgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImZsYXRseSINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQoNCiMgQXJ0aWZpY2lhbCBpbnRlbGxpZ2VuY2UgYW5kIG1hY2hpbmUgbGVhcm5pbmcgaW4gZmluYW5jaWFsIHNlcnZpY2VzDQoNCkNyZWRpdCByYXRpbmcgcGxheXMgYW4gaW1wb3J0YW50IHJvbGUgaW4gcHJvZml0IG9wdGltaXphdGlvbiBhbmQgc3VzdGFpbmFibGUgZGV2ZWxvcG1lbnQgb2YgYmFua3MgaW4gcGFydGljdWxhciBhcyB3ZWxsIGFzIG90aGVyIGZpbmFuY2lhbCBpbnN0aXR1dGlvbnMuIE5vdywgdGhlIE1hY2hpbmUgTGVhcm5pbmcgYXBwcm9hY2ggaGFzIHByb3ZlbiB0byBiZSBzdXBlcmlvciBpbiB0ZXJtcyBvZiBhY2N1cmFjeSBhcyB3ZWxsIGFzIHJlbGlhYmlsaXR5IGNvbXBhcmVkIHRvIHNvbWUgdHJhZGl0aW9uYWwgY2xhc3NpZmljYXRpb24gbW9kZWxzLiBUaGUgZXZlbnRzIG9mIHRoZSBmaW5hbmNpYWwgY3Jpc2lzIGxlZCB0byB0aGUgY29sbGFwc2Ugb2YgYSBzZXJpZXMgb2YgZmluYW5jaWFsIGluc3RpdHV0aW9ucyBpbiBnZW5lcmFsIGFuZCBiYW5rcywgaW4gcGFydGljdWxhciwgaGF2ZSBhd2FrZW5lZCB0aGVzZSBvcmdhbml6YXRpb25zIHRvIG1vcmUgZW1waGFzaXMgb24gdGhlIHJvbGUgb2YgY3JlZGl0IGFwcHJhaXNhbHMgaW4gdGhlaXIgb3BlcmF0aW9ucy4gTW9zdCBvZiB0aGUgYmFua3OSIHByb2ZpdHMgY29tZSBmcm9tIGNyZWRpdCBhbmQgbG9hbiBhY3Rpdml0aWVzLiBDcmVkaXQgZ3JhbnRpbmcgaXMgb25lIG9mIHRoZSBhY3Rpdml0aWVzIHRoYXQgZ2VuZXJhdGUgYSBsYXJnZSBwcm9wb3J0aW9uIG9mIHJldmVudWUgYW5kIHByb2ZpdCBmb3IgdGhlIGJhbmsgYnV0IGFsc28gaW1wbGllcyBhIGxvdCBvZiByaXNrcyBbWmFrcnpld3NrYSwgMjAwN10uIFRoZSBtYWluIHJpc2sgb2YgYSBiYW5rIGlzIHRoZSBjbGllbnRzIGFyZSBub3QgYWJsZSB0byByZXBheSB0aGUgbG9hbnMgdGhleSBoYXZlIGdyYW50ZWQuIE9uIHRoZSBvdGhlciBoYW5kLCB0aGUgZGVjaXNpb24gd2hldGhlciBvciBub3QgdG8gcHJvdmlkZSBhIGxvYW4gdG8gdGhlIGNsaWVudCBvZnRlbiBkZXBlbmRzIG9uIHRoZSBxdWFsaWZpY2F0aW9ucyBhbmQgZXhwZXJpZW5jZSBvZiB0aGUgY3JlZGl0IGFzc2Vzc29yIFtUaG9tYXMsIDIwMDBdLg0KDQpJbiBhZGRpdGlvbiwgdGhlIGJhc2lzIGZvciBncmFudGluZyBjcmVkaXQgdG8gYSBjdXN0b21lciBpcyBiYXNlZCBvbiBhIG51bWJlciBvZiBjcml0ZXJpYSB0aGF0IHNvbWUgb2YgdGhlbSBhcmUgdmVyeSBkaWZmaWN1bHQgdG8gbWVhc3VyZSwgb3IgZGlmZmljdWx0IHRvIG1lYXN1cmUgYWNjdXJhdGVseS4gRm9yIGV4YW1wbGUsIHRoZSA1IHN0YW5kYXJkIGNyaXRlcmlhIGZvciBjcmVkaXQgZ3JhbnRpbmcgaXMgYmFzZWQgb24gYmFuayBhc3Nlc3NtZW50cyBvZiBzdGF0dXMsIGNhcGFjaXR5LCBjYXBpdGFsLCBjb2xsYXRlcmFsLCBhbmQgYm9ycm93ZXKScyBjb25kaXRpb25zIFtBYnJhaGFtcyAmIFpoYW5nLCAyMDA4XS4gQ2xlYXJseSwgc29tZSBjcml0ZXJpYSwgc3VjaCBhcyB0aGUgc3RhdHVzIGFuZCBjYXBhY2l0eSBvZiBhIGJvcnJvd2VyLCBhcmUgZGlmZmljdWx0IHRvIGFzc2VzcyBhbmQgbWF5LCB0aGVyZWZvcmUsIGxlYWQgdG8gZXJyb3JzIGluIGxlbmRpbmcgZGVjaXNpb25zLiBJbiBhZGRpdGlvbiwgdGhlIGNyZWRpdCByYXRpbmcgYXBwcm9hY2ggYmFzZWQgb24gdGhlc2UgNSBzdGFuZGFyZCBjcml0ZXJpYSBpcyBjb3N0bHkgYW5kIHRoZXJlIG1pZ2h0IG9jY3VyIGluY29uc2lzdGVuY3kgb24gdGhlIGRlY2lzaW9uIGJldHdlZW4gZGlmZmVyZW50IGNyZWRpdCBhc3Nlc3NvcnMgZm9yIHRoZSBzYW1lIGxvYW4gYXBwbGljYXRpb24uIER1ZSB0byB0aGVzZSBjb25zdHJhaW50cywgYmFua3MsIGFzIHdlbGwgYXMgZmluYW5jaWFsIGluc3RpdHV0aW9ucywgbmVlZCB0byB1c2UgcmVsaWFibGUsIG9iamVjdGl2ZSBhbmQgbG93IGNvc3QgY3JlZGl0aW5nIGFuZCBjcmVkaXQgcmF0aW5ncyB0byBoZWxwIHRoZW0gZGVjaWRlIHdoZXRoZXIgdG8gZ3JhbnQgb3Igbm90LiBDcmVkaXRzIGZvciBsb2FuIGFwcGxpY2F0aW9ucyBbQWtoYXZlaW4sIEZyYW1lLCAmIFdoaXRlLCAyMDA1OyBDaHllLCBDaGluLCAmIFBlbmcsIDIwMDRdLiBNb3Jlb3ZlciwgYWNjb3JkaW5nIHRvIFRob21hcyBhbmQgQ1RHICgyMDAyKV0sIGJhbmtzIG5lZWQgYSBjcmVkaXQgcmF0aW5nIHRoYXQgbWVldHMgdGhlIGZvbGxvd2luZyByZXF1aXJlbWVudHM6ICgxKSBjaGVhcCBhbmQgZWFzeSB0byBvcGVyYXRlLCAoMikgZmFzdCBhbmQgc3RhYmxlLCAoMykgbWFrZSBjb25zaXN0ZW50IGRlY2lzaW9ucyBiYXNlZCBvbiB1bmJpYXNlZCBpbmZvcm1hdGlvbiBpbmRlcGVuZGVudCBvZiBzdWJqZWN0aXZlIGZlZWxpbmdzIGFuZCBlbW90aW9ucywgYW5kICg0KSB0aGUgZWZmZWN0aXZlbmVzcyBvZiB0aGUgY3JlZGl0IHJhdGluZyBzeXN0ZW0gY2FuIGJlIGVhc2lseSBjaGVjayBhbmQgYWRqdXN0IGF0IGFueSB0aW1lIHRvIHRpbWVseSBhZGp1c3Qgd2l0aCBjaGFuZ2VzIGluIHBvbGljaWVzIG9yIGNvbmRpdGlvbnMgb2YgdGhlIGVjb25vbXkuDQoNCkZvciBjcmVkaXQgY2xhc3NpZmljYXRpb24sIHRoZSB0cmFkaXRpb25hbCBhcHByb2FjaCBpcyBiYXNlZCBvbiBwdXJlIHN0YXRpc3RpY2FsIG1ldGhvZHMgc3VjaCBhcyBtdWx0aXZhcmlhdGUgbGluZWFyIHJlZ3Jlc3Npb24gW01leWVyICYgUGlmZXIsIDE5NzBdLCBkaXNjcmltaW5hbnQgYW5hbHlzaXMgW0FsdG1hbiwgMTk2OCwgQmFuYXNpaywgQ3Jvb2ssICYgVGhvbWFzLCAyMDAzXSwgYW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24gW0Rlc2FpLCBDcm9vaywgJiBPdmVyc3RyZWV0LCAxOTk2OyBEaW1pdHJhcywgWmFuYWtpcywgJiBab3BvdW5pZGlzLCAxOTk2OyBFbGxpb3R0ICYgRmlsaW5rb3YsIDIwMDg7IExlZSwgQ2hpdSwgTHUsICYgQ2hlbiwgMjAwMl0uIEhvd2V2ZXIsIHRoZSByZXF1aXJlbWVudHMgb2YgdGhlIEJhc2VsIENvbW1pdHRlZSBvbiBCYW5raW5nIFN1cGVydmlzaW9uIGlzc3VlZCBpbiAyMDA0IHJlcXVpcmUgYmFua3MgYW5kIGZpbmFuY2lhbCBpbnN0aXR1dGlvbnMgdG8gdXNlIGNyZWRpdCBjbGFzc2lmaWNhdGlvbiBtb2RlbHMgd2hpY2ggYXJlIG1vcmUgcmVsaWFibGUgaW4gb3JkZXIgdG8gaW1wcm92ZSB0aGUgZWZmaWNpZW5jeSBvZiBjYXBpdGFsIGFsbG9jYXRpb24uIEluIG9yZGVyIHRvIG1lZXQgdGhlc2UgZGVtYW5kcywgaW4gcmVjZW50IHllYXJzLCB0aGVyZSBoYXZlIGJlZW4gc29tZSBuZXcgbW9kZWxzIG9mIGNyZWRpdCBjbGFzc2lmaWNhdGlvbiBiYXNlZCBvbiBtYWNoaW5lIGxlYXJuaW5nIGFuZCBhcnRpZmljaWFsIGludGVsbGlnZW5jZSAoQUwpLiBVbmxpa2UgcHJldmlvdXMgYXBwcm9hY2hlcywgdGhlc2UgbmV3IG1ldGhvZHMgZG8gbm90IGdpdmUgYW55IHN0cmljdCBhc3N1bXB0aW9ucyBhcyB0byB0aGUgbmVlZCBmb3Igc3RhdGlzdGljYWwgYXBwcm9hY2hlcy4gSW5zdGVhZCwgdGhlc2UgbmV3IGFwcHJvYWNoZXMgYXR0ZW1wdCB0byBleHBsb2l0IGFuZCBwcm92aWRlIHRoZSBrbm93bGVkZ2UsIHRoZSBvdXRwdXQgaW5mb3JtYXRpb24gYmFzZWQgb25seSBvbiBpbnB1dHMgdGhhdCBhcmUgb2JzZXJ2YXRpb25zLCBpbmZvcm1hdGlvbiBmcm9tIHRoZSBwYXN0LiBXaXRoIHRoZSBjcmVkaXQgY2xhc3NpZmljYXRpb24gcHJvYmxlbSwgc29tZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBzdWNoIGFzIEFydGlmaWNpYWwgTmV1cmFsIE5ldHdvcmsgKEFOTikuIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSwgSyBOZWFyZXN0IE5laWdoYm9ycyhLTk4pICwgUmFuZG9tIEZvcmVzdCAoUkYpLCBEZWNpc2lvbiBUcmVlIChEVCksIGhhcyBwcm92ZWQgdG8gYmUgc3VwZXJpb3IgaW4gdGVybXMgb2YgYWNjdXJhY3kgYXMgd2VsbCBhcyByZWxpYWJpbGl0eSBjb21wYXJlZCB0byBzb21lIHRyYWRpdGlvbmFsIGNsYXNzaWZpY2F0aW9uIG1vZGVscyBbQ2hpIGV0IGFsLiwgMjAwNCwgSHVhbmcgZXQgYWwuLCAmIFdhbmcsIDIwMDc7IEluY2UgJiBBa3RhbiwgMjAwOTsgTWFydGVucyBldCBhbC4gLiwgMjAxMF0uDQoNCiMgTWV0aG9kb2xvZ3kNClRoZSBjb3N0IG9mIG1pc2NsYXNzaWZ5aW5nIGEgYmFkIHJlY29yZCBpbnRvIGEgZ29vZCByZWNvcmQgaXMgbXVjaCBoaWdoZXIgdGhhbiBtaXNjbGFzc2lmeWluZyBhIGdvb2QgcmVjb3JkIGludG8gYmFkIHJlY29yZHMgaXJyZXNwZWN0aXZlIG9mIHdoYXQgY2xhc3NpZmljYXRpb24gbW9kZWwgdG8gdXNlLiBSYXRoZXIgdGhhbiB1c2luZyBBY2N1cmFjeSBhcyB0aGUgbWFpbiBjcml0ZXJpb24gZm9yIHNlbGVjdGluZyBhbmQgZXZhbHVhdGluZyBtb2RlbHMsIGFub3RoZXIgYmV0dGVyIHdheSBpcyB0byB1c2UgdGhlIJNwcm9maXQgY3JpdGVyaW9ulCwgYXMga25vd24gYXMgdGhlIGVjb25vbWljIGNvbnNlcXVlbmNlcyBvZiB1c2luZyB0aGUgbW9kZWwsIGFzIHRoZSBzZWxlY3Rpb24gY3JpdGVyaW9uIGluIHRoaXMgYXJ0aWNsZS4gU3BlY2lmaWNhbGx5LCB0aGlzIGFwcHJvYWNoIGlzIGRlc2NyaWJlZCBpbiBkZXRhaWwgYXMgYmVsb3c6DQoNCioqRmlyc3QqKiAsIHVzaW5nIDQgTWFjaGluZSBMZWFybmluZyBtb2RlbHMvYXBwcm9hY2hlcyBmb3IgdGhlIHByb2JsZW0gb2YgY2xhc3NpZmljYXRpb24uIEZvciBlYWNoIG9mIHRoZSBjbGFzc2lmaWNhdGlvbiB0aHJlc2hvbGRzIGV4YW1pbmUgdGhlIHZhcmlhYmlsaXR5IG9mIHRoZSB0aHJlZSBjcml0ZXJpYSBmb3IgZXZhbHVhdGluZyB0aGUgcXVhbGl0eSBvZiB0aGUgY2xhc3NpZmljYXRpb24gbW9kZWw6DQoNCi0gQmFkLUJhZCBQcmVkaWN0aW9uIFJhdGUsDQoNCi0gR29vZCAtIEdvb2QgUHJlZGljdGlvbiBSYXRlLA0KDQotIEFjY3VyYWN5IFJhdGUgKEFjY3VyYWN5IFJhdGUpLg0KDQoqKk5leHQqKiwgYXNzZXNzaW5nIHRoZSBxdWFsaXR5IG9mIGNsYXNzaWZpY2F0aW9uIG9mIGVhY2ggYXBwcm9hY2ggdG8gdGhlIHR3byBncm91cHMgb2YgbG9hbiBhcHBsaWNhdGlvbnMgKGJhZCBhbmQgZ29vZCBncm91cHMpIGFzIHdlbGwgYXMgdGhlIGdlbmVyYWwgYWNjdXJhY3kgbGV2ZWwgb2YgY2xhc3NpZmljYXRpb24gbW9kZWxzIHdoZW4gdGhlIHRocmVzaG9sZCBjaGFuZ2VzOg0KDQotIFVzZSB0aGUgTW9udGUgQ2FybG8gc2ltdWxhdGlvbiBtZXRob2QgKDEwMDAgdGltZXMpLg0KDQotIEludGVyZXN0IHJhdGUgKEFmdGVyIGRlZHVjdGluZyB0aGUgY29zdHMgbGlrZSB0aGUgc3RhZmYuLCBldGMuKSBpcyA1JSBwLmEuDQoNCi0gRGlzdHJpYnV0aW9uIG9mIHRoZSBsb2FuIGFtb3VudCAoZm9yIGxvYW4gYXBwcm92YWwgcmVjb3JkcyBiYXNlZCBvbiBiYW5rIGxlbmRpbmcgaGlzdG9yeSBkYXRhKS4NCg0KIyBUaGUgZGF0YXNldA0KDQpUaGUgYWN0dWFsIGRhdGFzZXQgYGhtZXEuY3N2YCBmcm9tIJNDUkVESVQgUklTSyBBTkFMWVRJQ1MgTUVBU1VSRU1FTlQgVEVDSE5JUVVFUywgQVBQTElDQVRJT05TLCBhbmQgRVhBTVBMRVMgaW4gU0FTlCBhcmUgYmVlbiB1c2luZyBpbiB0aGlzIGFydGljbGUuIFRoaXMgZGF0YXNldCBjYW4gYmUgZG93bmxvYWRlZCBkaXJlY3RseSBmcm9tIHRoZSBsaW5rOiBodHRwOi8vd3d3LmNyZWRpdHJpc2thbmFseXRpY3MubmV0L2RhdGFzZXRzLXByaXZhdGUuaHRtbA0KDQpUaGUgZGF0YSBzZXQgSE1FUSByZXBvcnRzIGNoYXJhY3RlcmlzdGljcyBhbmQgZGVsaW5xdWVuY3kgaW5mb3JtYXRpb24gZm9yIDUsOTYwIGhvbWUgZXF1aXR5IGxvYW5zLiBBIGhvbWUgZXF1aXR5IGxvYW4gaXMgYSBsb2FuIHdoZXJlIHRoZSBvYmxpZ29yIHVzZXMgdGhlIGVxdWl0eSBvZiBoaXMgb3IgaGVyIGhvbWUgYXMgdGhlIHVuZGVybHlpbmcgY29sbGF0ZXJhbC4gVGhlIGRhdGEgc2V0IGhhcyB0aGUgZm9sbG93aW5nIGNoYXJhY3RlcmlzdGljczoNCg0KLSBCQUQ6IDEgPSBhcHBsaWNhbnQgZGVmYXVsdGVkIG9uIGxvYW4gb3Igc2VyaW91c2x5IGRlbGlucXVlbnQ7IDAgPSBhcHBsaWNhbnQgcGFpZCBsb2FuDQotIExPQU46IEFtb3VudCBvZiB0aGUgbG9hbiByZXF1ZXN0DQotIE1PUlREVUU6IEFtb3VudCBkdWUgb24gZXhpc3RpbmcgbW9ydGdhZ2UNCi0gVkFMVUU6IFZhbHVlIG9mIGN1cnJlbnQgcHJvcGVydHkNCi0gUkVBU09OOiBEZWJ0Q29uID0gZGVidCBjb25zb2xpZGF0aW9uOyBIb21lSW1wID0gaG9tZSBpbXByb3ZlbWVudA0KLSBKT0I6IE9jY3VwYXRpb25hbCBjYXRlZ29yaWVzDQotIFlPSjogWWVhcnMgYXQgcHJlc2VudCBqb2INCi0gREVST0c6IE51bWJlciBvZiBtYWpvciBkZXJvZ2F0b3J5IHJlcG9ydHMNCi0gREVMSU5ROiBOdW1iZXIgb2YgZGVsaW5xdWVudCBjcmVkaXQgbGluZXMNCi0gQ0xBR0U6IEFnZSBvZiBvbGRlc3QgY3JlZGl0IGxpbmUgaW4gbW9udGhzDQotIE5JTlE6IE51bWJlciBvZiByZWNlbnQgY3JlZGl0IGlucXVpcmllcw0KLSBDTE5POiBOdW1iZXIgb2YgY3JlZGl0IGxpbmVzDQotIERFQlRJTkM6IERlYnQtdG8taW5jb21lIHJhdGlvDQoNCiMgTW9kZWxsaW5nDQpIZXJlIGFyZSB0aGUgUiBjb2RlcyBhbmQgdGhlIGV4cGVyaW1lbnRhbCByZXN1bHRzIG9idGFpbmVkIGZyb20gdGhlIGhtZXEuY3N2IGRhdGFzZXQ6DQoNCiMjIFByZS1wcm9jZXNzaW5nIGRhdGENCg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIExvYWRpbmcgZGF0YQ0KDQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtYWdyaXR0cikNCmhtZXEgPC0gcmVhZC5jc3YoImh0dHA6Ly93d3cuY3JlZGl0cmlza2FuYWx5dGljcy5uZXQvdXBsb2Fkcy8xLzkvNS8xLzE5NTExNjAxL2htZXEuY3N2Iiwgc2VwID0gIiwiLCBoZWFkZXIgPSBUUlVFKQ0KaGVhZChobWVxKQ0KDQpgYGANCiANCiANCg0KIyMgU29tZSBkYXRhIHByb2Nlc3NpbmcgZnVuY3Rpb25zIGZvciBmaWxsaW5nIGFuZCByZS1sYWJlbGluZyBtaXNzaW5nIGRhdGENCg0KYGBge3J9DQojIyAxDQpyZXBsYWNlX25hX21lYW4gPC0gZnVuY3Rpb24oeCl7DQogIG1lYW4gPC0gbWVhbih4LCBuYS5ybSA9IFRSVUUpDQogIHhbaXMubmEoeCldIDwtIG1lYW4NCiAgcmV0dXJuKHgpDQp9DQoNCg0KDQojIyAyDQpuYW1lX2pvYiA8LSBmdW5jdGlvbih4KXsNCiAgeCAlPD4lIGFzLmNoYXJhY3RlcigpDQogIEVMU0UgPC0gVFJVRQ0KICBqb2JfbmFtZSA8LSBjKCJNZ3IiLCAiT2ZmaWNlIiwgIk90aGVyIiwgIlByb2ZFeGUiLCAiU2FsZXMiLCAiU2VsZiIpDQogIGNhc2Vfd2hlbigheCAlaW4lIGpvYl9uYW1lIH4gIk90aGVyIiwgDQogICAgICAgICAgICBFTFNFIH4geCkgDQoNCn0gDQoNCg0KDQojIyAzDQpuYW1lX3JlYXNvbiA8LSBmdW5jdGlvbih4KXsNCiAgRUxTRSA8LSBUUlVFDQogIHggJTw+JSBhcy5jaGFyYWN0ZXIoKQ0KICBjYXNlX3doZW4oIXggICVpbiUgIGMoIkRlYnRDb24iLCAiSG9tZUltcCIpIH4gIlVua25vd24iLA0KICAgICAgICAgICAgRUxTRSB+IHgpDQogICAgDQp9DQoNCg0KDQojIyA0DQpsYWJlbF9yZW5hbWUgPC0gZnVuY3Rpb24oeCl7DQogIGNhc2Vfd2hlbih4PT0xIH4gIkJBRCIsDQogICAgICAgICAgICB4PT0wIH4gIkdPT0QiKQ0KfQ0KDQoNCg0KDQojIyA1DQpteV9zY2FsZTAxIDwtIGZ1bmN0aW9uKHgpew0KICAoeC1taW4oeCkpLyhtYXgoeCkgLSBtaW4oeCkpDQp9ICMjIFJlc2NhbGVkIHRoZSBkYXRhLyBub3JtYWxpemF0aW9uDQojIEFwcGxpZWQgdGhlc2UgZnVuY3Rpb25zDQoNCg0KDQojIyBGaW5hbCBkYXRhIGZvciBzbGl0dGluZyANCmRmIDwtIGhtZXEgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgcmVwbGFjZV9uYV9tZWFuKSAlPiUgDQogICBtdXRhdGVfYXQoIlJFQVNPTiIsIG5hbWVfcmVhc29uKSAlPiUgDQogIG11dGF0ZV9hdCgiSk9CIiwgbmFtZV9qb2IpICU+JSANCiAgbXV0YXRlKEJBRCA9IGxhYmVsX3JlbmFtZShCQUQpKSAlPiUgDQogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgbXlfc2NhbGUwMSkNCg0KDQpoZWFkKGRmKQ0KYGBgDQoNCiMjIFRyYWluIGFuZCB0ZXN0IGRhdGFzZXRzDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9DQojIFByZXBhcmUgdHJhaW4gYW5kIHRlc3QgZGF0YXNldHMgDQpsaWJyYXJ5KGNhcmV0KQ0Kc2V0LnNlZWQoMSkNCmlkIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGRmJEJBRCwgcCA9IDAuNSwgbGlzdCA9IEZBTFNFKQ0KdHJhaW4gPC0gZGZbaWQsXQ0KdGVzdCA8LSBkZltpZCxdDQoNCg0KDQoNCiMgU2V0IHVwIHBhcmFtZXRlcml6YXRpb24gYW5kIGNyb3NzLXZhbGlkYXRpb246DQpzZXQuc2VlZCgxKQ0KdHJhaW5Db250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGVhdHMgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc1Byb2JzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcnlGdW5jdGlvbiA9IG11bHRpQ2xhc3NTdW1tYXJ5KSANCg0KDQpgYGANCg0KDQojIyBQYXJhbGxlbCBjb21wdXRpbmcNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQojIFNldCB1cCBwYXJhbGxlbCBjb21wdXRpbmcgbW9kZQ0KDQpsaWJyYXJ5KGRvUGFyYWxsZWwpDQpuX2NvcmVzIDwtIGRldGVjdENvcmVzKCkNCg0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNvcmVzID0gbl9jb3JlcyAtIDEpDQojIFdyaXRlIHRoZSBhdmVyYWdlIGNhbGN1bGF0aW9uIGZ1bmN0aW9ucyBmb3IgDQojIHRoZSBjbGFzc2lmaWNhdGlvbiBjcml0ZXJpYSBvZiB0aGUgbW9kZWwgDQojIHdpdGggdGhlIHNlbGVjdGlvbiBvZiAxMDAwIG9ic2VydmF0aW9uIHBhdHRlcm5zIGZyb20gdGVzdGluZyBkYXRhIDEwMCB0aW1lcy4NCg0KDQpgYGANCg0KDQojIyBGdW5jdGlvbmFsIHByb2dyYW1taW5nIHNldHVwIGZvciBldmFsdWF0aW9uDQoNCmBgYHtyfQ0KDQpldmFsX2Z1bjEgPC0gZnVuY3Rpb24odGhyZXNob2xkLCBtb2RlbF9zZWxlY3RlZCl7DQogIG15X2RmIDwtIGRhdGEuZnJhbWUoKQ0KICBmb3IgKGkgaW4gMToxMDApew0KICAgIHNldC5zZWVkKDEpICMjIHNldC5zZWVkIGFmdGVyIGNhbGxpbmcgbXlfZGYgdG8gbWFrZSB0aGUgc2FtcGxlcyB1bmR1cGxpY2F0ZWQNCiAgICANCiAgICBpZCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSB0ZXN0JEJBRCwgcCA9IDEwMDAvbnJvdyh0ZXN0KSwgbGlzdCA9IEZBTFNFKQ0KICAgDQogICAgIHRlc3RfZGYgPC0gdGVzdFtpZCxdDQogICAgDQogICAgIHByZWRpY3QgPC0gcHJlZGljdChtb2RlbF9zZWxlY3RlZCwgdGVzdF9kZiwgdHlwZSA9ICJwcm9iIikgJT4lDQogICAgICBwdWxsKEJBRCkNCiAgICANCiAgICAgcHJlZGljdCA8LSBjYXNlX3doZW4ocHJlZGljdCA+PSB0aHJlc2hvbGQgfiAiQkFEIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdCA8IHRocmVzaG9sZCB+ICJHT09EIikNCiAgICAgDQogICAgIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeCh0ZXN0X2RmJEJBRCwgcHJlZGljdCAlPiUgIGFzLmZhY3RvcigpKQ0KICAgICAjIyB0aGUgY29uZnVzaW9uIG1hdHJpeCBpcyBpbiAyIGJ5IDINCiAgICAgDQogICAgIGJnX2dnIDwtIGNtJHRhYmxlICU+JSANCiAgICAgICBhcy52ZWN0b3IoKSAlPiUgDQogICAgICAgbWF0cml4KG5jb2wgPSA0KSAlPiUgDQogICAgICAgYXMuZGF0YS5mcmFtZSgpDQogICAgICMgY29udmVydCBpdCBpbnRvIG1hdHJpeCBvZiAxIGJ5IDQNCiAgICAgDQogICAgbmFtZXMoYmdfZ2cpIDwtIGMoIkJCIiwgIkdCIiwgIkJHIiwgIkdHIikNCiAgICANCiAgICByZXN1bHQgPC0gYyhjbSRvdmVyYWxsLCBjbSRieUNsYXNzKQ0KICAgIG5hbWVzIDwtIHJlc3VsdCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByb3cubmFtZXMoKQ0KICAgIA0KICAgIHJlc3VsdCAlPiUgDQogICAgICBhcy52ZWN0b3IoKSAlPiUgDQogICAgICBtYXRyaXgobmNvbCA9IDE4KSAlPiUgDQogICAgICBhcy5kYXRhLmZyYW1lKCkgLT4gYWxsX2RmIA0KICAgIA0KICAgIG5hbWVzKGFsbF9kZikgPC0gbmFtZXMNCiAgICBhbGxfZGYgPC0gYmluZF9jb2xzKGFsbF9kZiwgYmdfZ2cpDQogICAgbXlfZGYgPC0gYmluZF9yb3dzKG15X2RmLCBhbGxfZGYpDQogICAgIH0NCiAgcmV0dXJuKG15X2RmKQ0KfQ0KIyMgV3JpdGUgYSBmdW5jdGlvbiBjYWxjdWxhdGluZyB0aGUgY2xhc3NpZmljYXRpb24gDQojIG9mIHRoZSBtb2RlbCBiYXNlZCBvbiBhIHByZS1zZWxlY3RlZCB0aHJlc2hvbGQgcmFuZ2U6DQoNCm15X3Jlc3VsdHNfZnJvbV90aHJlc19yYW5nZSA8LSBmdW5jdGlvbihsb3dfdGhyZXMsIHVwX3RocmVzLCBieSwgbW9kZWxfc2VsZWN0ZWQpIHsNCiAgbXlfcmFuZ2UgPC0gc2VxKGxvd190aHJlcywgdXBfdGhyZXMsIGJ5ID0gYnkpDQogIG4gPC0gbGVuZ3RoKG15X3JhbmdlKQ0KICBhbGxfZGYgPC0gZGF0YS5mcmFtZSgpDQogIA0KICBmb3IgKGkgaW4gMTpuKSB7DQogICAgZGYgPC0gZXZhbF9mdW4xKG15X3JhbmdlW2ldLCBtb2RlbF9zZWxlY3RlZCA9IG1vZGVsX3NlbGVjdGVkKQ0KICAgIGRmICU8PiUgbXV0YXRlKFRocmVzaG9sZCA9IHBhc3RlKCJUIiwgbXlfcmFuZ2VbaV0pKQ0KICAgIGFsbF9kZiA8LSBiaW5kX3Jvd3MoYWxsX2RmLCBkZikNCiAgfQ0KICByZXR1cm4oYWxsX2RmKQ0KICANCn0NCg0KYGBgDQoNCg0KIyMgVHJhaW4gYSB2YXJpZXR5IG9mIE1hY2hpbmUgTGVhcm5pbmcgbW9kZWxzDQoNCg0KIyMjIFJhbmRvbSBGb3Jlc3QNCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KbXlfcmYgPC0gdHJhaW4oQkFEIH4uLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwNCiAgICAgICAgICAgICAgIG1ldHJpYyA9ICJBVUMiLCANCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbCwNCiAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSA1DQogICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgIA0KYGBgDQoNCg0KIyMjIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUNCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KbXlfc3ZtIDwtIHRyYWluKEJBRCB+LiwNCiAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4sDQogICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVJhZGlhbCIsDQogICAgICAgICAgICAgICAgbWV0cmljID0gIkFVQyIsDQogICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sLA0KICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSA1KQ0KDQpgYGANCg0KDQojIyMgR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZQ0KDQpgYGB7cn0NCnNldC5zZWVkKDEpDQpteV9nYm0gPC0gdHJhaW4oQkFEIH4uLA0KICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbiwNCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2JtIiwNCiAgICAgICAgICAgICAgICBtZXRyaWMgPSAiQVVDIiwNCiAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wsDQogICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDUpDQoNCmBgYA0KDQoNCiMjIyBCb29zdGVkIENsYXNzaWZpY2F0aW9uIFRyZWVzIC0gQURBIEJvb3N0DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCm15X2FkYSA8LSB0cmFpbihCQUQgfi4sDQogICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLA0KICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJhZGEiLA0KICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJBVUMiLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbCwNCiAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gNSkgIA0KYGBgDQoNCg0KDQojIyBSZXN1bHRzIGZvciB0aG9zZSBtb2RlbHMgYWZ0ZXIgdGVzdGluZyANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBHZXR0aW5nIHJlc3VsdHMgZnJvIHRoZSB0aHJlc2hvbGRzIGZyb20gMC4xIC0gMC44DQoNCm15X3Jlc3VsdHNfZnJvbV90aHJlc19yYW5nZSgwLjEsIDAuOCwgMC4xLCBteV9yZikgLT4+IHJmX3Jlc3VsdHMNCg0KbXlfcmVzdWx0c19mcm9tX3RocmVzX3JhbmdlKDAuMSwgMC44LCAwLjEsIG15X3N2bSkgLT4+IHN2bV9yZXN1bHRzDQoNCm15X3Jlc3VsdHNfZnJvbV90aHJlc19yYW5nZSgwLjEsIDAuOCwgMC4xLCBteV9nYm0pIC0+PiBnYm1fcmVzdWx0cw0KDQpteV9yZXN1bHRzX2Zyb21fdGhyZXNfcmFuZ2UoMC4xLCAwLjgsIDAuMSwgbXlfYWRhKSAtPj4gYWRhX3Jlc3VsdHMNCg0KDQoNCg0KIyBTdW1tYXJ5IG9mIHJlc3VsdHMNCg0KdG90YWxfZGZfcmVzdWx0cyA8LSBiaW5kX3Jvd3MocmZfcmVzdWx0cyAlPiUgbXV0YXRlKE1vZGVsID0gIlJGIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ZtX3Jlc3VsdHMgJT4lIG11dGF0ZShNb2RlbCA9ICJTVk0iKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnYm1fcmVzdWx0cyAlPiUgbXV0YXRlKE1vZGVsID0gIkdCTSIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkYV9yZXN1bHRzICU+JSBtdXRhdGUoTW9kZWwgPSAiQURBIikgKQ0KDQoNCm5hbWVzKHRvdGFsX2RmX3Jlc3VsdHMpIDwtIG5hbWVzKHRvdGFsX2RmX3Jlc3VsdHMpICU+JSBzdHJfcmVwbGFjZV9hbGwoIiAiLCAiIikNCg0KDQojIyBNZWRpYW4gY2FsY3VsdXMNCg0KY29tcGFyZWRfcmVzdWx0cyA8LSB0b3RhbF9kZl9yZXN1bHRzICU+JSANCiAgZ3JvdXBfYnkoVGhyZXNob2xkLCBNb2RlbCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lZGlhbiksIEFjY3VyYWN5LCBOZWdQcmVkVmFsdWUsIFBvc1ByZWRWYWx1ZSkgJT4lIA0KICB1bmdyb3VwKCkgDQoNCmhlYWQoY29tcGFyZWRfcmVzdWx0cykNCg0KYGBgDQoNCg0KIyMgV3JpdGUgdGhlIHRocmVzaG9sZCBleHRyYWN0aW9uIGZ1bmN0aW9uOiANCg0KYGBge3J9DQpnZXRfbnVtYmVyIDwtIGZ1bmN0aW9uKHgpIHsNCiAgeCAlPiUgDQogICAgc3RyX3JlcGxhY2VfYWxsKCJbXjAtOV0iLCAiIikgJT4lIA0KICAgIGFzLm51bWVyaWMoKSAtPj4geQ0KICByZXR1cm4oeSowLjEpDQp9DQoNCmBgYA0KDQoNCiMjIFZpc3VhbGl6YXRpb24NCg0KIyMjIEdvb2QgLSBHb29kIFByZWRpY3Rpb24NCg0KYGBge3J9DQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQ0KDQpjb21wYXJlZF9yZXN1bHRzICAlPiUgIA0KICBtdXRhdGUoVGhyZXNob2xkID0gZ2V0X251bWJlcihUaHJlc2hvbGQpKSAlPiUgDQogIGdncGxvdChhZXMoVGhyZXNob2xkLCBOZWdQcmVkVmFsdWUsIGNvbG9yID0gTW9kZWwpKSArDQogIGdlb21fbGluZSgpKw0KICBnZW9tX3BvaW50KCkrDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAuMSwgMC44LCAwLjEpKSsNCiAgbGFicyh5PSAiR29vZCAtIEdvb2QgUHJlZGljdG9uIFJhdGUiLA0KICAgICAgIHRpdGxlID0gIkZpZ3VyZSAxIDogR29vZCAtIEdvb2QgcHJlZGljdGlvbiByYXRlIGJ5IHRocmVzaG9sZCINCiAgICAgICApDQoNCg0KYGBgDQoNCiMjIyBCYWQgLSBCYWQgUHJlZGljdGlvbg0KDQpgYGB7cn0NCmNvbXBhcmVkX3Jlc3VsdHMgICU+JSAgDQogIG11dGF0ZShUaHJlc2hvbGQgPSBnZXRfbnVtYmVyKFRocmVzaG9sZCkpICU+JSANCiAgZ2dwbG90KGFlcyhUaHJlc2hvbGQsIFBvc1ByZWRWYWx1ZSwgY29sb3IgPSBNb2RlbCkpICsNCiAgZ2VvbV9saW5lKCkrDQogIGdlb21fcG9pbnQoKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkrDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMC4xLCAwLjgsIDAuMSkpKw0KICBsYWJzKHk9ICJCYWQgLSBCYWQgUHJlZGljdG9uIFJhdGUiLA0KICAgICAgIHRpdGxlID0gIiBGaWd1cmUgMiA6IEJhZCAtIEJhZCBwcmVkaWN0aW9uIHJhdGUgYnkgdGhyZXNob2xkIg0KICApDQoNCmBgYA0KDQojIyMgVG90YWwgQWNjdXJhY3kgUmF0ZQ0KDQpgYGB7cn0NCmNvbXBhcmVkX3Jlc3VsdHMgICU+JSAgDQogIG11dGF0ZShUaHJlc2hvbGQgPSBnZXRfbnVtYmVyKFRocmVzaG9sZCkpICU+JSANCiAgZ2dwbG90KGFlcyhUaHJlc2hvbGQsIEFjY3VyYWN5LCBjb2xvciA9IE1vZGVsKSkgKw0KICBnZW9tX2xpbmUoKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLjEsIDAuOCwgMC4xKSkrDQogIGxhYnMoeT0gIiBBY2N1cmFjeSBSYXRlIiwNCiAgICAgICB0aXRsZSA9ICJGaWd1cmUgMyA6IEFjY3VyYWN5IHJhdGUgYnkgdGhyZXNob2xkIg0KICApDQoNCmBgYA0KDQpCZWNhdXNlIFRoZSBjb3N0IG9mIG1pc2NsYXNzaWZ5aW5nIGEgYmFkIHJlY29yZCBpbnRvIGEgZ29vZCByZWNvcmQgaXMgbXVjaCBoaWdoZXIgdGhhbiBtaXNjbGFzc2lmeWluZyBhIGdvb2QgcmVjb3JkIGludG8gYmFkIHJlY29yZHMgaXJyZXNwZWN0aXZlIG9mIHdoYXQgY2xhc3NpZmljYXRpb24gbW9kZWwgYXJlIHVzZWQuIFRoZXJlZm9yZSwgYWNjdWFyY3kgaXMgbm90IGFsd2F5cyB0aGUgYmVzdCBjcml0ZXJpYSB0byBjaG9vc2UuIFRocmVzaG9sZCA9IDAuNSBpcyBub3QgYXBwcm9wcmlhdGUgaW4gYWxsIHNpdHVhdGlvbnMuIFRoYXQgbWVhbnMsIGNoYW5naW5nIHRocmVzaG9sZCBmb3IgYSBzcGVjaWZpYyBtb2RlbCBpcyByZWFsbHkgbGlmZS1jaGFuZ2luZy4gSW4gdGhpcyBjYXNlLCBJIGdvIGZvciB0aGUgdGhyZXNob2xkIGluIHRoZSByYW5nZSBvZiAoMC4yLDAuNCkuDQoNCiMjIE5ldyBhcHByb2FjaCAtIEVzdGltYXRpb24gb2YgcHJvZml0IGlzIGJhc2VkIG9uIHNpbXVsYXRpb24gd2hlbiB0aHJlc2hvbGQgY2hhbmdlcw0KDQojIyMgR2V0dGluZyBHRyAtIEJHIHByZWRpY3Rpb25zDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgUmFuZG9tIEZvcmVzdA0KDQpyZl9yZXN1bHRzICU+JSANCiAgZ3JvdXBfYnkoVGhyZXNob2xkKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMoc3VtKSwgR0csIEJHKSAtPj4gZ2dfYmdfUkZfTW9kZWwNCg0KDQojIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUNCg0Kc3ZtX3Jlc3VsdHMgJT4lIA0KICBncm91cF9ieShUaHJlc2hvbGQpICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhzdW0pLCBHRywgQkcpIC0+PiBnZ19iZ19TVk1fTW9kZWwNCg0KICANCiMgR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZQ0KDQpnYm1fcmVzdWx0cyAlPiUgDQogIGdyb3VwX2J5KFRocmVzaG9sZCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKHN1bSksIEdHLCBCRykgLT4+IGdnX2JnX2dibV9Nb2RlbA0KDQoNCiMgQm9vc3RlZCBDbGFzc2lmaWNhdGlvbiBUcmVlcyAtIEFEQSBCb29zdA0KDQphZGFfcmVzdWx0cyAlPiUgDQogIGdyb3VwX2J5KFRocmVzaG9sZCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKHN1bSksIEdHLCBCRykgLT4+IGdnX2JnX0FEQV9Nb2RlbA0KDQoNCg0KIyMgV3JpdGUgdGhlIHByb2ZpdCBzaW11bGF0aW9uIGZ1bmN0aW9uIHdpdGggMTAwMCBzaW11bGF0aW9uczoNCg0KDQpwcm9maXRfc2ltdSA8LSBmdW5jdGlvbihkYXRhX2Zyb21fbW9kZWwsIHJvdywgcmF0ZSkgew0KICBwcm9mIDwtIGMoKQ0KICBmb3IgKGogaW4gMToxMDAwKSB7DQogICAgDQogICAgZ29vZF9sb2FuIDwtIGRhdGFfZnJvbV9tb2RlbFtyb3csIDJdICU+JSBhcy5udW1lcmljKCkNCiAgICBiYWRfbG9hbiA8LSBkYXRhX2Zyb21fbW9kZWxbcm93LCAzXSAlPiUgYXMubnVtZXJpYygpDQogICAgDQogICAgYW1vdW50X29mX2dvb2RfbG9hbiA8LSBzYW1wbGUoaG1lcSRMT0FOLCBnb29kX2xvYW4sIHJlcGxhY2UgPSBUUlVFKQ0KICAgIGFtb3VudF9vZl9iYWRfbG9hbiA8LSBzYW1wbGUoaG1lcSRMT0FOLCBiYWRfbG9hbiwgcmVwbGFjZSA9IFRSVUUpDQogICAgDQogICAgcmV0dXJuIDwtIHN1bShyYXRlKmFtb3VudF9vZl9nb29kX2xvYW4pIC0gc3VtKGFtb3VudF9vZl9iYWRfbG9hbikNCiAgICBwcm9mIDwtIGMocHJvZiwgcmV0dXJuKQ0KICB9DQogIA0KICBkYXRhLmZyYW1lKFByb2ZpdCA9IHByb2YsIFRocmVzaG9sZCA9IGdldF9udW1iZXIoZGF0YV9mcm9tX21vZGVsW3JvdywgMV0gJT4lIGFzLnZlY3RvcigpKSkgJT4lIA0KICAgIHJldHVybigpDQogIA0KfQ0KDQpgYGANCg0KIyMjIFByb2ZpdCBzaW11bGF0aW9uIA0KDQpgYGB7cn0NCiMgUHJvZml0IFNpbXVsYXRpb24gYnkgUmFuZG9tIEZvcmVzdA0KDQpyZl9wcm9maXQgPC0gYmluZF9yb3dzKHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCAxLCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCAyLCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCAzLCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCA0LCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCA1LCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCA2LCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCA3LCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1JGX01vZGVsLCA4LCAwLjA1ICkpDQoNCg0KIyBQcm9maXQgU2ltdWxhdGlvbiBieSBTdXBwb3J0IFZlY3RvciBNYWNoaW5lIA0KDQpzdm1fcHJvZml0IDwtIGJpbmRfcm93cyhwcm9maXRfc2ltdShnZ19iZ19TVk1fTW9kZWwsIDEsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1NWTV9Nb2RlbCwgMiwgMC4wNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfU1ZNX01vZGVsLCAzLCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwcm9maXRfc2ltdShnZ19iZ19TVk1fTW9kZWwsIDQsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1NWTV9Nb2RlbCwgNSwgMC4wNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfU1ZNX01vZGVsLCA2LCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwcm9maXRfc2ltdShnZ19iZ19TVk1fTW9kZWwsIDcsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX1NWTV9Nb2RlbCwgOCwgMC4wNSkpDQoNCg0KIyBQcm9maXQgU2ltdWxhdGlvbiBieSBHcmFkaWVudCBCb29zdGluZyBNYWNoaW5lDQoNCmdibV9wcm9maXQgPC0gYmluZF9yb3dzKHByb2ZpdF9zaW11KGdnX2JnX2dibV9Nb2RlbCwgMSwgMC4wNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfZ2JtX01vZGVsLCAyLCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwcm9maXRfc2ltdShnZ19iZ19nYm1fTW9kZWwsIDMsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX2dibV9Nb2RlbCwgNCwgMC4wNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfZ2JtX01vZGVsLCA1LCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwcm9maXRfc2ltdShnZ19iZ19nYm1fTW9kZWwsIDYsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX2dibV9Nb2RlbCwgNywgMC4wNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfZ2JtX01vZGVsLCA4LCAwLjA1KSkNCg0KDQojIFByb2ZpdCBTaW11bGF0aW9uIGJ5IEFEQQ0KDQphZGFfcHJvZml0IDwtIGJpbmRfcm93cyhwcm9maXRfc2ltdShnZ19iZ19BREFfTW9kZWwsIDEsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX0FEQV9Nb2RlbCwgMiwgMC4wNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfQURBX01vZGVsLCAzLCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwcm9maXRfc2ltdShnZ19iZ19BREFfTW9kZWwsIDQsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX0FEQV9Nb2RlbCwgNSwgMC4wNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfQURBX01vZGVsLCA2LCAwLjA1KSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwcm9maXRfc2ltdShnZ19iZ19BREFfTW9kZWwsIDcsIDAuMDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpdF9zaW11KGdnX2JnX0FEQV9Nb2RlbCwgOCwgMC4wNSkpDQoNCiAgDQpgYGANCg0KIyMgUHJvZml0cyBjb21wYXJpc29uDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KcHJvZml0X2NvbXBhcmVfbW9kZWxzIDwtIGJpbmRfcm93cyhyZl9wcm9maXQgJT4lIG11dGF0ZShNb2RlbCA9ICJSRiIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ZtX3Byb2ZpdCAlPiUgbXV0YXRlKE1vZGVsID0gIlNWTSIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2JtX3Byb2ZpdCAlPiUgbXV0YXRlKE1vZGVsID0gIkdCTSIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWRhX3Byb2ZpdCAlPiUgbXV0YXRlKE1vZGVsID0gIkFEQSIpKQ0KIyBWaXN1YWxpemF0aW9uDQoNCnByb2ZpdF9jb21wYXJlX21vZGVscyAlPiUgDQogIG11dGF0ZShQcm9maXQgPSBQcm9maXQvMTAwMDAwMCkgJT4lIA0KICBncm91cF9ieShUaHJlc2hvbGQsIE1vZGVsKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWVhbiksIFByb2ZpdCkgJT4lIA0KICBnZ3Bsb3QoYWVzKFRocmVzaG9sZCwgUHJvZml0LCBjb2xvciA9IE1vZGVsKSkgKw0KICBnZW9tX2xpbmUoKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAuMSwgMC44LCAwLjEpKSArDQogIGxhYnMoIHg9IE5VTEwsIHkgPSBOVUxMLCANCiAgICAgICAgdGl0bGUgPSAiRmlndXJlIDQ6IFNpbXVsYXRlZCBQcm9maXRzIGZyb20gNCBNYWNoaW5lIExlYXJuaW5nIE1vZGVsc1xuYmFzZWQgb24gTW9udGUgQ2FybG8gU2ltdWxhdGlvbiBieSBUaHJlc2hvbGQiICkNCg0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBTdW1tYXJ5DQoNCnByb2ZpdF9jb21wYXJlX21vZGVscyAlPiUgDQogIGZpbHRlcihUaHJlc2hvbGQgPT0gMC4yKSAlPiUgDQogIGdyb3VwX2J5KE1vZGVsKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWVhbiwgbWVkaWFuLCBtaW4sIG1heCwgc2QpLCBQcm9maXQpICU+JSANCiAgYXJyYW5nZSgtbWVhbikgJT4lIA0KICBrbml0cjo6a2FibGUoKQ0KDQpgYGANCg0KDQojIENhbiByZWZpbmluZyBwYXJhbWV0ZXJzIGZvciB0aGUgUmFuZG9tIEZvcmVzdCBpbXByb3ZlIHByb2ZpdGFiaWxpdHkgZm9yIGJhbmtzPw0KDQojIyBSRiBwZXJmb3JtYW5jZSB3aGVuIG10cnkgY2hhbmdlcw0KDQpgYGB7cn0NCg0KIyMgU2V0IHJlZmluaW5nIGdyaWQ6DQoNCm15X2dyaWQxIDwtIGV4cGFuZC5ncmlkKG10cnkgPSAxOjEwKQ0KDQoNCiMgIyBSRiB0dW5lZDogDQpzZXQuc2VlZCgxKQ0KbXlfcmZfdHVuZWQgPC0gdHJhaW4oQkFEIH4uLCANCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4sIA0KICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsIA0KICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJBVUMiLCANCiAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wsIA0KICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gbXlfZ3JpZDEpDQoNCg0KIyBXcml0ZSBmdW5jdGlvbiB2aXN1YWxpemVzIHNvbWUgY3JpdGVyaWEgZm9yIGV2YWx1YXRpbmcgdGhlIHF1YWxpdHkgb2YNCiMgbW9kZWwgUkYgY2xhc3NpZmljYXRpb24gd2hlbiBtdHJ5IGNoYW5nZXM6DQoNCm15X3Bsb3QgPC0gZnVuY3Rpb24obW9kZWwpIHsNCiAgdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkNCiAgdSA8LSBtb2RlbCRyZXN1bHRzICU+JSANCiAgICBzZWxlY3QobXRyeSwgQVVDLCBBY2N1cmFjeSwgS2FwcGEsIFNlbnNpdGl2aXR5LCBTcGVjaWZpY2l0eSwgUHJlY2lzaW9uKSAlPiUgDQogICAgZ2F0aGVyKGEsIGIsIC1tdHJ5KSANCiAgDQogIHUgJT4lIA0KICAgIGdncGxvdChhZXMobXRyeSwgYikpICsgDQogICAgZ2VvbV9saW5lKCkgKyANCiAgICBnZW9tX3BvaW50KCkgKyANCiAgICBmYWNldF93cmFwKH4gYSwgc2NhbGVzID0gImZyZWUiKSArIA0KICAgIGxhYnMoeCA9ICJOdW1iZXIgb2YgbXRyeSIsIHkgPSBOVUxMLCANCiAgICAgICAgIHRpdGxlID0gIlRoZSBSZWxhdGlvbnNoaXAgYmV0d2VlbiBNb2RlbCBQZXJmb3JtYW5jZSBhbmQgbXRyeSIpDQogIA0KfQ0KDQojIFZpc3VhbGlzYXRvbjoNCg0KbXlfcmZfdHVuZWQgJT4lIA0KICBteV9wbG90KCkgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxLCAxMCwgMSkpDQoNCmBgYA0KDQoNCiMjIGNvbXBhcmUgdGhlIG1vZGVsIHdpdGhvdXQgdHVuaW5nIGFuZCB3aXRoIHR1bmluZw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpteV9yZXN1bHRzX2Zyb21fdGhyZXNfcmFuZ2UoMC4xLCAwLjgsIDAuMSwgbXlfcmZfdHVuZWQpIC0+PiByZl9yZXN1bHRzX3R1bmVkDQoNCnJmX3Jlc3VsdHNfdHVuZWQgJT4lIA0KICBncm91cF9ieShUaHJlc2hvbGQpICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhzdW0pLCBHRywgQkcpIC0+PiBnZ19iZ19SRl9Nb2RlbF90dW5lZA0KDQoNCnJmX3Byb2ZpdF90dW5lZCA8LSBiaW5kX3Jvd3MocHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDEsIDAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDIsIDAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDMsIDAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDQsIDAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDUsIDAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDYsIDAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDcsIDAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZml0X3NpbXUoZ2dfYmdfUkZfTW9kZWxfdHVuZWQsIDgsIDAuMSkpDQoNCnR3b19yZl9tb2RlbCA8LSBiaW5kX3Jvd3MocHJvZml0X2NvbXBhcmVfbW9kZWxzICU+JSBmaWx0ZXIoTW9kZWwgPT0gIlJGIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICByZl9wcm9maXRfdHVuZWQgJT4lIG11dGF0ZShNb2RlbCA9ICJSRiB0dW5lZCIpKQ0KDQp0d29fcmZfbW9kZWwgJT4lIA0KICBtdXRhdGUoUHJvZml0ID0gUHJvZml0IC8gMTAwMDAwMCkgJT4lIA0KICBncm91cF9ieShUaHJlc2hvbGQsIE1vZGVsKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWVhbiksIFByb2ZpdCkgJT4lIA0KICBnZ3Bsb3QoYWVzKFRocmVzaG9sZCwgUHJvZml0LCBjb2xvciA9IE1vZGVsKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMC4xLCAwLjgsIDAuMSkpICsgDQogIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMLCANCiAgICAgICB0aXRsZSA9ICJGaWd1cmUgNTogU2ltdWxhdGVkIFByb2ZpdCBmcm9tIERhZmF1bHQgYW5kIHR1bmVkIFJhbmRvbSBGb3Jlc3RcbmJhc2VkIG9uIE1vbnRlIENhcmxvIFNpbXVsYXRpb24gYnkgVGhyZXNob2xkIiwgDQogICAgICAgc3VidGl0bGUgPSAiRGF0YSBVc2VkOiBodHRwOi8vd3d3LmNyZWRpdHJpc2thbmFseXRpY3MubmV0L2RhdGFzZXRzLXByaXZhdGUuaHRtbCIpDQoNCmBgYA0KDQoNCiMgUmVzdWx0cw0KDQotIFRoZSBwcm9maXQgbWFyZ2luIG9mIHRoZSB0dW5lZCBSRiBtb2RlbCBpcyBoaWdoZXIgYXQgYW55IHRocmVzaG9sZCBwb2ludC4NCg0KLSBUaGVyZSBpcyBhbHdheXMgYSB0cmFkZW9mZiBiZXR3ZWVuIHRoZSBhY2N1cmFjeSBvZiBnb29kIHJlY29yZHMgYW5kIGJhZCByZWNvcmRzIChGaWd1cmUgMSBhbmQgRmlndXJlIDIpLg0KDQotIEF0IHRoZSBsZXZlbCBvZiBjbGFzc2lmaWNhdGlvbiB0aGF0IHRoZSBoaWdoZXN0IGFjY3VyYWN5IGlzLCBpdCBpcyB0aGUgY2xhc3NpZmljYXRpb24gdGhyZXNob2xkIHRoYXQgZ2VuZXJhdGVzIG5vbi1vcHRpbWFsIHJldHVybnMgZm9yIHRoZSBiYW5rLg0KDQotIFdoZW4gdGhlIGNsYXNzaWZpY2F0aW9uIHRocmVzaG9sZCBpbmNyZWFzZXMsIHRoZSBwcm9maXRhYmlsaXR5IG9mIHVzaW5nIHRoZSBjbGFzc2lmaWNhdGlvbiBtb2RlbCBkZWNyZWFzZXMuDQoNCi0gQXQgdGhlIGRlZmF1bHQgdGhyZXNob2xkIG9mIDAuNSAtIFRoaXMgaXMgYWxzbyB0aGUgZGVmYXVsdCB0aHJlc2hvbGQgZm9yIG1vc3QgY29tbWVyY2lhbCBzb2Z0d2FyZSBzdWNoIGFzIFNQU1MsIFN0YXRhLCBhbmQgRXZpZXdzLCB0aGUgZWNvbm9taWMgY29uc2VxdWVuY2VzIChNb250ZSBDYXJsby1iYXNlZCBhdmVyYWdlIHByb2ZpdGFiaWxpdHkpIG9mIHVzaW5nIHRoZSBjbGFzc2lmaWNhdGlvbiBtb2RlbCBhcmUgbG91c3kuDQoNCi0gRm9yIHRoZSBwdXJwb3NlIG9mIG1heGltaXppbmcgcHJvZml0IGFuZCB1c2luZyBSRiB3aXRob3V0IHR1bmluZywgUmFuZG9tIEZvcmVzdCBzaG91bGQgYmUgdXNlZCwgYW5kIHRoZSB1c2FnZSB0aHJlc2hvbGQgaXMgMC4yLiBXaXRoIHR1bmVkIFJGIHRoZSBiZXN0IHRocmVzaG9sZCB3b3VsZCBiZSAwLjQuDQoNCg0K