Introduction

I will write something …

rm(list = ls())
library(tidyverse)

# Load data: 
hmeq <- read_csv("http://www.creditriskanalytics.net/uploads/1/9/5/1/19511601/hmeq.csv")


# Convert to factor for categorical columns and remove missing cases: 

hmeq_full <- hmeq %>% 
  na.omit() %>% 
  mutate_if(is.character, as.factor)

# Split data: 

set.seed(29)
train <- hmeq_full %>% 
  group_by(BAD) %>% 
  sample_frac(0.7) %>% 
  ungroup()
  
test <- anti_join(hmeq_full, train)

actual <- test$BAD

# Logistic Classifier: 
logistic_reg <- glm(BAD ~ ., data = train, family = binomial(link = "logit"))

# Use Logistic for predicting PD (Probability of Default): 
pd <- predict(logistic_reg, test, type = "response")


is_Bad <- actual == 1
orders_by_pd <- order(pd, decreasing = TRUE)
is_Bad_ordered <- is_Bad[orders_by_pd]

data.frame(actual = actual, 
           is_Bad = is_Bad, 
           is_Bad_ordered = is_Bad_ordered,
           orders_by_pd = orders_by_pd, 
           prob_of_default = pd) -> df_BG

df_BG %>% mutate(TPR = cumsum(is_Bad_ordered) / sum(is_Bad_ordered), 
                 FPR = cumsum(!is_Bad_ordered) / sum(!is_Bad_ordered)) -> df_BG

# Function calculates credit score from PD predicted: 

credit_score <- function(pd) {
  
  pdo <- 20
  factor <- pdo / log(2)
  my_offset <- 600 - factor*log(50)

  scores <- my_offset + factor*log((1 - pd) / pd)
  
  return(scores)
  
}


scores <- credit_score(pd = pd)
orders_by_score <- order(scores, decreasing = FALSE)


df_BG %>% 
  mutate(is_Bad_ordered_by_score = is_Bad[orders_by_score], 
         orders_by_score = orders_by_score,
         credit_score = scores) %>% 
  mutate(TPR_score = cumsum(is_Bad_ordered_by_score) / sum(is_Bad_ordered_by_score), 
         FPR_score = cumsum(!is_Bad_ordered_by_score) / sum(!is_Bad_ordered_by_score)) -> df_BG 


data.frame(FPR = c(df_BG$FPR, df_BG$FPR), 
           TPR = c(df_BG$TPR, df_BG$TPR_score), 
           Method = rep(c("PD", "Score"), each = nrow(df_BG))) -> df_roc


library(extrafont)
theme_set(theme_minimal())
my_font <- "Roboto Condensed"

df_roc %>% 
  ggplot(aes(FPR, TPR, color = Method)) + 
  geom_line() + 
  facet_wrap(~ Method) + 
  labs(title = "Figure 1: ROC curve by PD and Score") + 
  theme(text = element_text(family = my_font)) + 
  theme(plot.margin = unit(rep(0.8, 4), "cm"))

##  prob_of_default      credit_score  
##  Min.   :0.0000665   Min.   :187.0  
##  1st Qu.:0.0215613   1st Qu.:554.5  
##  Median :0.0429043   Median :576.7  
##  Mean   :0.0854957   Mean   :573.8  
##  3rd Qu.:0.0882429   3rd Qu.:597.2  
##  Max.   :0.9999696   Max.   :764.6
## 
## Call:
## roc.default(response = actual, predictor = pd)
## 
## Data: pd in 919 controls (actual 0) < 90 cases (actual 1).
## Area under the curve: 0.7563
## 
## Call:
## roc.default(response = actual, predictor = scores)
## 
## Data: scores in 919 controls (actual 0) > 90 cases (actual 1).
## Area under the curve: 0.7563
## # A tibble: 2 x 2
## # Groups:   predicted [2]
##   predicted     n
##       <dbl> <int>
## 1         0    68
## 2         1    22
## # A tibble: 2 x 2
## # Groups:   predicted [2]
##   predicted     n
##       <dbl> <int>
## 1         0   914
## 2         1     5
##         TPR         FPR  Accuracy Specificity Threshold
## 1 0.2444444 0.005440696 0.9276511   0.9945593       0.5
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 914  68
##          1   5  22
##                                           
##                Accuracy : 0.9277          
##                  95% CI : (0.9099, 0.9429)
##     No Information Rate : 0.9108          
##     P-Value [Acc > NIR] : 0.03135         
##                                           
##                   Kappa : 0.3493          
##                                           
##  Mcnemar's Test P-Value : 3.971e-13       
##                                           
##             Sensitivity : 0.24444         
##             Specificity : 0.99456         
##          Pos Pred Value : 0.81481         
##          Neg Pred Value : 0.93075         
##              Prevalence : 0.08920         
##          Detection Rate : 0.02180         
##    Detection Prevalence : 0.02676         
##       Balanced Accuracy : 0.61950         
##                                           
##        'Positive' Class : 1               
## 

calculate_profit <- function(threshold) {
  
  # Convert to label 0 or 1 base on PD predicted: 
  labels_predicted <- case_when(pd >= threshold ~ 1, TRUE ~ 0)
  
  # Set conditions for calculating average profit at given threshold: 
  n <- 100
  rate <- 0.07
  profit_space <- NULL
  
  # Calculate net profit for each sample randomly selected from test data:
  
  for (j in 1:n) {
  
    set.seed(j)
    
    df_results <- test %>% 
      mutate(predicted = labels_predicted) %>% 
      sample_frac(0.7)
    
    # Profit from true negative cases: 
    
    df_results %>% 
      filter(predicted == 0, BAD == 0) %>% 
      mutate(profit = rate*LOAN) %>% 
      pull(profit) %>% 
      sum() -> total_profit
    
    # Loss causes from false negative cases: 
    
    df_results %>% 
      filter(predicted == 0, BAD == 1) %>% 
      pull(LOAN) %>% 
      sum() -> total_loss
    
    # Net profit: 
    net_profit <- total_profit - total_loss
    
    profit_space <- c(profit_space, net_profit)
    
  }
  
  # Average net profit at given threshold: 
  data.frame(Profit_avg = mean(profit_space), Threshold = threshold) -> df_prof_thres
  
  return(df_prof_thres)
  
}

lapply(threshold_range, calculate_profit) -> list_prof

do.call("bind_rows", list_prof) -> df_prof

df_prof %>% filter(Threshold == 0.5) -> default_prof

df_prof %>% slice(which.max(Profit_avg)) -> max_prof

df_prof %>% 
  ggplot(aes(Threshold, Profit_avg)) + 
  geom_line(color = "#00006E") + 
  geom_point(data = max_prof, color = "red", size = 3) + 
  geom_point(data = default_prof, color = "blue", size = 3) + 
  geom_text(data = max_prof %>% mutate(Threshold = Threshold + 0.1), family = my_font,  
            aes(label = "Threshold that\nmaximizes Profit"), size = 3.5) + 
  geom_text(data = default_prof %>% mutate(Profit_avg = Profit_avg + 35000, Threshold = Threshold + 0.05),
            aes(label = "Profit at\ndefault threshold"), size = 3.5, family = my_font) + 
  scale_y_continuous(labels = scales::dollar_format()) + 
  theme(text = element_text(family = my_font)) + 
  theme(plot.margin = unit(rep(0.8, 4), "cm")) + 
  labs(y = NULL, title = "Figure 3: Net Profit by Threshold")

LS0tDQp0aXRsZTogIlVuZGVyc3RhbmQgUk9DIEN1cnZlIGFuZCBTZWFyY2ggVGhyZXNob2xkIHRoYXQgbWF4aW1pemVzIFByb2ZpdCAiDQphdXRob3I6ICJOZ3V5ZW4gQ2hpIER1bmciDQpzdWJ0aXRsZTogIlIgTWFjaGluZSBMZWFybmluZyBTZXJpZXMiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAjIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogemVuYnVybg0KICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImZsYXRseSINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA2KQ0KYGBgDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KSSB3aWxsIHdyaXRlIHNvbWV0aGluZyAuLi4gDQoNCmBgYHtyfQ0KDQoNCnJtKGxpc3QgPSBscygpKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCiMgTG9hZCBkYXRhOiANCmhtZXEgPC0gcmVhZF9jc3YoImh0dHA6Ly93d3cuY3JlZGl0cmlza2FuYWx5dGljcy5uZXQvdXBsb2Fkcy8xLzkvNS8xLzE5NTExNjAxL2htZXEuY3N2IikNCg0KDQojIENvbnZlcnQgdG8gZmFjdG9yIGZvciBjYXRlZ29yaWNhbCBjb2x1bW5zIGFuZCByZW1vdmUgbWlzc2luZyBjYXNlczogDQoNCmhtZXFfZnVsbCA8LSBobWVxICU+JSANCiAgbmEub21pdCgpICU+JSANCiAgbXV0YXRlX2lmKGlzLmNoYXJhY3RlciwgYXMuZmFjdG9yKQ0KDQojIFNwbGl0IGRhdGE6IA0KDQpzZXQuc2VlZCgyOSkNCnRyYWluIDwtIGhtZXFfZnVsbCAlPiUgDQogIGdyb3VwX2J5KEJBRCkgJT4lIA0KICBzYW1wbGVfZnJhYygwLjcpICU+JSANCiAgdW5ncm91cCgpDQogIA0KdGVzdCA8LSBhbnRpX2pvaW4oaG1lcV9mdWxsLCB0cmFpbikNCg0KYWN0dWFsIDwtIHRlc3QkQkFEDQoNCiMgTG9naXN0aWMgQ2xhc3NpZmllcjogDQpsb2dpc3RpY19yZWcgPC0gZ2xtKEJBRCB+IC4sIGRhdGEgPSB0cmFpbiwgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKQ0KDQojIFVzZSBMb2dpc3RpYyBmb3IgcHJlZGljdGluZyBQRCAoUHJvYmFiaWxpdHkgb2YgRGVmYXVsdCk6IA0KcGQgPC0gcHJlZGljdChsb2dpc3RpY19yZWcsIHRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQoNCmlzX0JhZCA8LSBhY3R1YWwgPT0gMQ0Kb3JkZXJzX2J5X3BkIDwtIG9yZGVyKHBkLCBkZWNyZWFzaW5nID0gVFJVRSkNCmlzX0JhZF9vcmRlcmVkIDwtIGlzX0JhZFtvcmRlcnNfYnlfcGRdDQoNCmRhdGEuZnJhbWUoYWN0dWFsID0gYWN0dWFsLCANCiAgICAgICAgICAgaXNfQmFkID0gaXNfQmFkLCANCiAgICAgICAgICAgaXNfQmFkX29yZGVyZWQgPSBpc19CYWRfb3JkZXJlZCwNCiAgICAgICAgICAgb3JkZXJzX2J5X3BkID0gb3JkZXJzX2J5X3BkLCANCiAgICAgICAgICAgcHJvYl9vZl9kZWZhdWx0ID0gcGQpIC0+IGRmX0JHDQoNCmRmX0JHICU+JSBtdXRhdGUoVFBSID0gY3Vtc3VtKGlzX0JhZF9vcmRlcmVkKSAvIHN1bShpc19CYWRfb3JkZXJlZCksIA0KICAgICAgICAgICAgICAgICBGUFIgPSBjdW1zdW0oIWlzX0JhZF9vcmRlcmVkKSAvIHN1bSghaXNfQmFkX29yZGVyZWQpKSAtPiBkZl9CRw0KDQojIEZ1bmN0aW9uIGNhbGN1bGF0ZXMgY3JlZGl0IHNjb3JlIGZyb20gUEQgcHJlZGljdGVkOiANCg0KY3JlZGl0X3Njb3JlIDwtIGZ1bmN0aW9uKHBkKSB7DQogIA0KICBwZG8gPC0gMjANCiAgZmFjdG9yIDwtIHBkbyAvIGxvZygyKQ0KICBteV9vZmZzZXQgPC0gNjAwIC0gZmFjdG9yKmxvZyg1MCkNCg0KICBzY29yZXMgPC0gbXlfb2Zmc2V0ICsgZmFjdG9yKmxvZygoMSAtIHBkKSAvIHBkKQ0KICANCiAgcmV0dXJuKHNjb3JlcykNCiAgDQp9DQoNCg0Kc2NvcmVzIDwtIGNyZWRpdF9zY29yZShwZCA9IHBkKQ0Kb3JkZXJzX2J5X3Njb3JlIDwtIG9yZGVyKHNjb3JlcywgZGVjcmVhc2luZyA9IEZBTFNFKQ0KDQoNCmRmX0JHICU+JSANCiAgbXV0YXRlKGlzX0JhZF9vcmRlcmVkX2J5X3Njb3JlID0gaXNfQmFkW29yZGVyc19ieV9zY29yZV0sIA0KICAgICAgICAgb3JkZXJzX2J5X3Njb3JlID0gb3JkZXJzX2J5X3Njb3JlLA0KICAgICAgICAgY3JlZGl0X3Njb3JlID0gc2NvcmVzKSAlPiUgDQogIG11dGF0ZShUUFJfc2NvcmUgPSBjdW1zdW0oaXNfQmFkX29yZGVyZWRfYnlfc2NvcmUpIC8gc3VtKGlzX0JhZF9vcmRlcmVkX2J5X3Njb3JlKSwgDQogICAgICAgICBGUFJfc2NvcmUgPSBjdW1zdW0oIWlzX0JhZF9vcmRlcmVkX2J5X3Njb3JlKSAvIHN1bSghaXNfQmFkX29yZGVyZWRfYnlfc2NvcmUpKSAtPiBkZl9CRyANCg0KDQpkYXRhLmZyYW1lKEZQUiA9IGMoZGZfQkckRlBSLCBkZl9CRyRGUFIpLCANCiAgICAgICAgICAgVFBSID0gYyhkZl9CRyRUUFIsIGRmX0JHJFRQUl9zY29yZSksIA0KICAgICAgICAgICBNZXRob2QgPSByZXAoYygiUEQiLCAiU2NvcmUiKSwgZWFjaCA9IG5yb3coZGZfQkcpKSkgLT4gZGZfcm9jDQoNCg0KbGlicmFyeShleHRyYWZvbnQpDQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQ0KbXlfZm9udCA8LSAiUm9ib3RvIENvbmRlbnNlZCINCg0KZGZfcm9jICU+JSANCiAgZ2dwbG90KGFlcyhGUFIsIFRQUiwgY29sb3IgPSBNZXRob2QpKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICBmYWNldF93cmFwKH4gTWV0aG9kKSArIA0KICBsYWJzKHRpdGxlID0gIkZpZ3VyZSAxOiBST0MgY3VydmUgYnkgUEQgYW5kIFNjb3JlIikgKyANCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSBteV9mb250KSkgKyANCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KHJlcCgwLjgsIDQpLCAiY20iKSkNCg0Kc3VtbWFyeShkZl9CRyAlPiUgc2VsZWN0KHByb2Jfb2ZfZGVmYXVsdCwgY3JlZGl0X3Njb3JlKSkNCg0KbGlicmFyeShwUk9DKQ0Kcm9jKGFjdHVhbCwgcGQpDQpyb2MoYWN0dWFsLCBzY29yZXMpDQoNCiMgU2V0IHRocmVzaG9sZCBmb3IgY2xhc3NpZmljYXRpb246IA0KdGhyZXNob2xkIDwtIDAuNQ0KDQpsYWJlbHNfcHJlZGljdGVkIDwtIGNhc2Vfd2hlbihwZCA+PSB0aHJlc2hvbGQgfiAxLCBUUlVFIH4gMCkNCg0KZGZfcmVzdWx0cyA8LSBkYXRhLmZyYW1lKHByZWRpY3RlZCA9IGxhYmVsc19wcmVkaWN0ZWQsIGFjdHVhbCA9IGFjdHVhbCwgUEQgPSBwZCkNCg0KZGZfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihhY3R1YWwgPT0gMSkgJT4lIA0KICBncm91cF9ieShwcmVkaWN0ZWQpICU+JSANCiAgY291bnQoKQ0KDQpkZl9yZXN1bHRzICU+JSANCiAgZmlsdGVyKGFjdHVhbCA9PSAwKSAlPiUgDQogIGdyb3VwX2J5KHByZWRpY3RlZCkgJT4lIA0KICBjb3VudCgpDQoNCmNhbGN1bGF0ZV9UUFJfRlBSIDwtIGZ1bmN0aW9uKHRocmVzaG9sZCkgew0KICANCiAgIyBDbGFzc2lmeSBhcyAxIG9yIDAgYmFzZSBvbiBQRCBhbmQgZ2l2ZW4gdGhyZXNob2xkOiANCiAgbGFiZWxzX3ByZWRpY3RlZCA8LSBjYXNlX3doZW4ocGQgPj0gdGhyZXNob2xkIH4gMSwgVFJVRSB+IDApDQogIA0KICBkZl9yZXN1bHRzIDwtIHRlc3QgJT4lIHRyYW5zbXV0ZShhY3R1YWwgPSBCQUQsIHByZWRpY3RlZCA9IGxhYmVsc19wcmVkaWN0ZWQpDQogIA0KICAjIEFjdHVhbCBjYXNlcyB0aGF0IGFsbCBhcmUgZGVmYXVsdCAobGFiZWwgYXMgMSk6IA0KICANCiAgZGZfcmVzdWx0cyAlPiUgDQogICAgZmlsdGVyKGFjdHVhbCA9PSAxKSAlPiUgDQogICAgcHVsbChwcmVkaWN0ZWQpIC0+IGJhZF9wcmVkaWN0ZWRfYnlfbW9kZWwNCiAgDQogICMgQ2FsY3VsYXRlIFRQUjogDQogIHN1bShiYWRfcHJlZGljdGVkX2J5X21vZGVsID09IDEpIC8gbGVuZ3RoKGJhZF9wcmVkaWN0ZWRfYnlfbW9kZWwpIC0+IFRQUg0KICANCiAgIyBBY3R1YWwgY2FzZXMgdGhhdCBhbGwgYXJlIG5vbi1kZWZhdWx0IChsYWJlbCBhcyAwKTogDQogIGRmX3Jlc3VsdHMgJT4lIA0KICAgIGZpbHRlcihhY3R1YWwgPT0gMCkgJT4lIA0KICAgIHB1bGwocHJlZGljdGVkKSAtPiBnb29kX3ByZWRpY3RlZF9ieV9tb2RlbA0KICANCiAgIyBDYWxjdWxhdGUgU3BlY2lmaWNpdHk6IA0KICBzdW0oZ29vZF9wcmVkaWN0ZWRfYnlfbW9kZWwgPT0gMCkgLyBsZW5ndGgoZ29vZF9wcmVkaWN0ZWRfYnlfbW9kZWwpIC0+IHNwZWMNCiAgDQogICMgQ2FsY3VsYXRlIEFjY3VyYWN5OiANCiAgc3VtKGRmX3Jlc3VsdHMkcHJlZGljdGVkID09IGRmX3Jlc3VsdHMkYWN0dWFsKSAvIGxlbmd0aChsYWJlbHNfcHJlZGljdGVkKSAtPiBhY2MNCiAgDQogICMgUmVwb3J0IHJlc3VsdHMgaW4gZGF0YSBmcmFtZTogDQogIGRmX2NsYXNzaWZpY2F0aW9uIDwtIGRhdGEuZnJhbWUoVFBSID0gVFBSLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUFIgPSAxIC0gc3BlYywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQWNjdXJhY3kgPSBhY2MsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNwZWNpZmljaXR5ID0gc3BlYywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhyZXNob2xkID0gdGhyZXNob2xkKQ0KICANCiAgcmV0dXJuKGRmX2NsYXNzaWZpY2F0aW9uKQ0KICANCn0NCg0KDQpjYWxjdWxhdGVfVFBSX0ZQUih0aHJlc2hvbGQgPSAwLjUpDQoNCmxpYnJhcnkoY2FyZXQpDQpjb25mdXNpb25NYXRyaXgobGFiZWxzX3ByZWRpY3RlZCAlPiUgYXMuZmFjdG9yKCksIGFjdHVhbCAlPiUgYXMuZmFjdG9yKCksIHBvc2l0aXZlID0gIjEiKQ0KDQoNCnRocmVzaG9sZF9yYW5nZSA8LSBjKDAuNSwgc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSAxMDApKQ0KbGFwcGx5KHRocmVzaG9sZF9yYW5nZSwgY2FsY3VsYXRlX1RQUl9GUFIpIC0+IG15X2xpc3QNCmRvLmNhbGwoImJpbmRfcm93cyIsIG15X2xpc3QpIC0+IGRmX3JvYw0KDQpkZl9yb2MgJT4lIA0KICBnYXRoZXIoTWV0cmljLCBSYXRlLCAtVGhyZXNob2xkKSAtPiBkZl9sb25nDQoNCmRmX2xvbmcgJT4lIA0KICBmaWx0ZXIoTWV0cmljID09ICJBY2N1cmFjeSIpICU+JSANCiAgc2xpY2Uod2hpY2gubWF4KFJhdGUpKSAtPiBtYXhfYWNjDQoNCmRmX2xvbmcgJT4lIA0KICBnZ3Bsb3QoYWVzKFRocmVzaG9sZCwgUmF0ZSwgY29sb3IgPSBNZXRyaWMpKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBtYXhfYWNjLCBzaGFwZSA9IDE4LCBzaXplID0gMykgKyANCiAgZ2VvbV90ZXh0KGRhdGEgPSBtYXhfYWNjICU+JSBtdXRhdGUoUmF0ZSA9IDAuODcpLCANCiAgICAgICAgICAgIGFlcyhsYWJlbCA9ICJUaHJlc2hvbGQgdGhhdFxubWF4aW1pemVzIEFjY3VyYWN5IiksIA0KICAgICAgICAgICAgc2l6ZSA9IDMuMiwgY29sb3IgPSAiYmxhY2siLCBmYW1pbHkgPSBteV9mb250KSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArIA0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9IG15X2ZvbnQpKSArIA0KICB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQocmVwKDAuOCwgNCksICJjbSIpKSArIA0KICBsYWJzKHkgPSBOVUxMLCB0aXRsZSA9ICJGaWd1cmUgMjogTW9kZWwgUGVyZm9ybWFuY2UgYnkgVGhyZXNob2xkIikNCg0KDQpjYWxjdWxhdGVfcHJvZml0IDwtIGZ1bmN0aW9uKHRocmVzaG9sZCkgew0KICANCiAgIyBDb252ZXJ0IHRvIGxhYmVsIDAgb3IgMSBiYXNlIG9uIFBEIHByZWRpY3RlZDogDQogIGxhYmVsc19wcmVkaWN0ZWQgPC0gY2FzZV93aGVuKHBkID49IHRocmVzaG9sZCB+IDEsIFRSVUUgfiAwKQ0KICANCiAgIyBTZXQgY29uZGl0aW9ucyBmb3IgY2FsY3VsYXRpbmcgYXZlcmFnZSBwcm9maXQgYXQgZ2l2ZW4gdGhyZXNob2xkOiANCiAgbiA8LSAxMDANCiAgcmF0ZSA8LSAwLjA3DQogIHByb2ZpdF9zcGFjZSA8LSBOVUxMDQogIA0KICAjIENhbGN1bGF0ZSBuZXQgcHJvZml0IGZvciBlYWNoIHNhbXBsZSByYW5kb21seSBzZWxlY3RlZCBmcm9tIHRlc3QgZGF0YToNCiAgDQogIGZvciAoaiBpbiAxOm4pIHsNCiAgDQogICAgc2V0LnNlZWQoaikNCiAgICANCiAgICBkZl9yZXN1bHRzIDwtIHRlc3QgJT4lIA0KICAgICAgbXV0YXRlKHByZWRpY3RlZCA9IGxhYmVsc19wcmVkaWN0ZWQpICU+JSANCiAgICAgIHNhbXBsZV9mcmFjKDAuNykNCiAgICANCiAgICAjIFByb2ZpdCBmcm9tIHRydWUgbmVnYXRpdmUgY2FzZXM6IA0KICAgIA0KICAgIGRmX3Jlc3VsdHMgJT4lIA0KICAgICAgZmlsdGVyKHByZWRpY3RlZCA9PSAwLCBCQUQgPT0gMCkgJT4lIA0KICAgICAgbXV0YXRlKHByb2ZpdCA9IHJhdGUqTE9BTikgJT4lIA0KICAgICAgcHVsbChwcm9maXQpICU+JSANCiAgICAgIHN1bSgpIC0+IHRvdGFsX3Byb2ZpdA0KICAgIA0KICAgICMgTG9zcyBjYXVzZXMgZnJvbSBmYWxzZSBuZWdhdGl2ZSBjYXNlczogDQogICAgDQogICAgZGZfcmVzdWx0cyAlPiUgDQogICAgICBmaWx0ZXIocHJlZGljdGVkID09IDAsIEJBRCA9PSAxKSAlPiUgDQogICAgICBwdWxsKExPQU4pICU+JSANCiAgICAgIHN1bSgpIC0+IHRvdGFsX2xvc3MNCiAgICANCiAgICAjIE5ldCBwcm9maXQ6IA0KICAgIG5ldF9wcm9maXQgPC0gdG90YWxfcHJvZml0IC0gdG90YWxfbG9zcw0KICAgIA0KICAgIHByb2ZpdF9zcGFjZSA8LSBjKHByb2ZpdF9zcGFjZSwgbmV0X3Byb2ZpdCkNCiAgICANCiAgfQ0KICANCiAgIyBBdmVyYWdlIG5ldCBwcm9maXQgYXQgZ2l2ZW4gdGhyZXNob2xkOiANCiAgZGF0YS5mcmFtZShQcm9maXRfYXZnID0gbWVhbihwcm9maXRfc3BhY2UpLCBUaHJlc2hvbGQgPSB0aHJlc2hvbGQpIC0+IGRmX3Byb2ZfdGhyZXMNCiAgDQogIHJldHVybihkZl9wcm9mX3RocmVzKQ0KICANCn0NCg0KbGFwcGx5KHRocmVzaG9sZF9yYW5nZSwgY2FsY3VsYXRlX3Byb2ZpdCkgLT4gbGlzdF9wcm9mDQoNCmRvLmNhbGwoImJpbmRfcm93cyIsIGxpc3RfcHJvZikgLT4gZGZfcHJvZg0KDQpkZl9wcm9mICU+JSBmaWx0ZXIoVGhyZXNob2xkID09IDAuNSkgLT4gZGVmYXVsdF9wcm9mDQoNCmRmX3Byb2YgJT4lIHNsaWNlKHdoaWNoLm1heChQcm9maXRfYXZnKSkgLT4gbWF4X3Byb2YNCg0KZGZfcHJvZiAlPiUgDQogIGdncGxvdChhZXMoVGhyZXNob2xkLCBQcm9maXRfYXZnKSkgKyANCiAgZ2VvbV9saW5lKGNvbG9yID0gIiMwMDAwNkUiKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBtYXhfcHJvZiwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDMpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IGRlZmF1bHRfcHJvZiwgY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAzKSArIA0KICBnZW9tX3RleHQoZGF0YSA9IG1heF9wcm9mICU+JSBtdXRhdGUoVGhyZXNob2xkID0gVGhyZXNob2xkICsgMC4xKSwgZmFtaWx5ID0gbXlfZm9udCwgIA0KICAgICAgICAgICAgYWVzKGxhYmVsID0gIlRocmVzaG9sZCB0aGF0XG5tYXhpbWl6ZXMgUHJvZml0IiksIHNpemUgPSAzLjUpICsgDQogIGdlb21fdGV4dChkYXRhID0gZGVmYXVsdF9wcm9mICU+JSBtdXRhdGUoUHJvZml0X2F2ZyA9IFByb2ZpdF9hdmcgKyAzNTAwMCwgVGhyZXNob2xkID0gVGhyZXNob2xkICsgMC4wNSksDQogICAgICAgICAgICBhZXMobGFiZWwgPSAiUHJvZml0IGF0XG5kZWZhdWx0IHRocmVzaG9sZCIpLCBzaXplID0gMy41LCBmYW1pbHkgPSBteV9mb250KSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXJfZm9ybWF0KCkpICsgDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gbXlfZm9udCkpICsgDQogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChyZXAoMC44LCA0KSwgImNtIikpICsgDQogIGxhYnMoeSA9IE5VTEwsIHRpdGxlID0gIkZpZ3VyZSAzOiBOZXQgUHJvZml0IGJ5IFRocmVzaG9sZCIpDQoNCmBgYA0KDQo=