This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

###############################################################
# FINAL FULL SOLUTION — COMPLETE CODE FOR ALL QUESTIONS (Q1–Q2)
###############################################################
# =============================
# Load Required Packages
# =============================
pkgs <- c("caret", "ipred", "rpart.plot", "randomForest", "caTools")
new_pkgs <- pkgs[!pkgs %in% installed.packages()[,"Package"]]
if (length(new_pkgs) > 0) install.packages(new_pkgs)
library(caret)
library(ipred)
library(rpart.plot)
library(randomForest)
library(caTools)
# =============================
# Q1(a): Load and Prepare Data
# =============================
cat("\n--- Q1(a): Loading Data ---\n")

--- Q1(a): Loading Data ---
csv_path <- "C:\\Users\\Lenovo\\OneDrive\\Desktop\\fall 2025\\stat\\Assignment 4-Stat\\compas.csv"
if (!file.exists(csv_path)) {
  stop("ERROR: compas.csv is NOT in working directory: ", getwd())
}
compas_data <- read.csv(csv_path)
cat("Loaded compas.csv with", nrow(compas_data), "rows.\n")
Loaded compas.csv with 6172 rows.
# Expected fields:
required_cols <- c("is_recid","race","sex","age",
                   "priors_count","c_charge_degree")
missing <- setdiff(required_cols, names(compas_data))
if (length(missing)>0) {
  stop("Missing columns in compas.csv: ", paste(missing, collapse=", "))
}
# Filter two races only
filtered <- subset(compas_data,
                   race %in% c("African-American", "Caucasian"))
# Factor conversions
filtered$is_recid <- factor(filtered$is_recid)
levels(filtered$is_recid) <- c("No_Recid","Yes_Recid")
filtered$race  <- factor(filtered$race)
filtered$sex   <- factor(filtered$sex)
filtered$c_charge_degree <- factor(filtered$c_charge_degree)
# Save factor levels for Q1(d)
all_races   <- levels(filtered$race)
all_sexes   <- levels(filtered$sex)
all_charges <- levels(filtered$c_charge_degree)
# Split data
set.seed(123)
idx <- createDataPartition(filtered$is_recid, p=0.8, list=FALSE)
train <- filtered[idx, ]
test  <- filtered[-idx, ]
cat("Training rows:", nrow(train), "Testing rows:", nrow(test), "\n")
Training rows: 4223 Testing rows: 1055 
# =============================
# Q1(b): Bagging Model
# =============================
cat("\n--- Q1(b): Bagging Model ---\n")

--- Q1(b): Bagging Model ---
formula_all <- is_recid ~ race + sex + age + priors_count + c_charge_degree
ctrl_bag <- trainControl(method="none", classProbs=TRUE)
set.seed(123)
bag_model <- train(
  form = formula_all,
  data = train,
  method = "treebag",
  nbagg = 50,
  trControl = ctrl_bag
)
print(bag_model)
Bagged CART 

4223 samples
   5 predictor
   2 classes: 'No_Recid', 'Yes_Recid' 

No pre-processing
Resampling: None 
# =============================
# Q1(c): Plot 3 Bagged Trees - CORRECTED
# =============================
cat("\n--- Q1(c): Saving First 3 Trees ---\n")

--- Q1(c): Saving First 3 Trees ---
# Build ipred version for tree extraction
set.seed(123)
bag_ipred <- ipred::bagging(formula_all, data = train, nbagg = 50)
save_tree <- function(tree_obj, name) {
  out <- file.path(getwd(), name)
  
  tryCatch({
    png(out, width = 1000, height = 800)
    # Extract the rpart object from the bagging tree
    rpart_obj <- tree_obj$btree
    rpart.plot(rpart_obj, main = name, roundint = FALSE)
    dev.off()
    cat("Saved", name, "\n")
  }, error = function(e) {
    cat("Error saving", name, ":", e$message, "\n")
    # Try to plot on screen as fallback
    try({
      rpart_obj <- tree_obj$btree
      rpart.plot(rpart_obj, main = name)
    }, silent = TRUE)
  })
}
# Plot first 3 trees
for (i in 1:3) {
  tree_name <- paste0("q1c_tree_", i, ".png")
  save_tree(bag_ipred$mtrees[[i]], tree_name)
}
Saved q1c_tree_1.png 
Saved q1c_tree_2.png 
Saved q1c_tree_3.png 
# =============================
# Q1(d): Predict New Individuals
# =============================
cat("\n--- Q1(d): New Arrestee Predictions ---\n")

--- Q1(d): New Arrestee Predictions ---
new_people <- data.frame(
  race = factor(c("Caucasian","Caucasian","African-American"),
                levels = all_races),
  sex  = factor(c("Male","Female","Male"), levels = all_sexes),
  age  = c(19,22,34),
  priors_count = c(1,2,0),
  c_charge_degree = factor(c("F","M","M"), levels = all_charges)
)
probs_new <- predict(bag_model, new_people, type="prob")
results_new <- data.frame(
  Person=c("Archie","Betty","Chuck"),
  Probability=probs_new$Yes_Recid
)
print(results_new)
# =============================
# Q1(e): Fairness Metrics
# =============================
cat("\n--- Q1(e): Fairness Metrics ---\n")

--- Q1(e): Fairness Metrics ---
probs_test <- predict(bag_model, test, type="prob")
pred_test <- ifelse(probs_test$Yes_Recid>0.5,"Yes_Recid","No_Recid")
df_test <- data.frame(
  Actual=test$is_recid,
  Predicted=pred_test,
  Race=test$race
)
metric_fun <- function(df){
  cm <- confusionMatrix(
    factor(df$Predicted, levels=c("No_Recid","Yes_Recid")),
    factor(df$Actual, levels=c("No_Recid","Yes_Recid")),
    positive="Yes_Recid"
  )
  t <- cm$table
  TN <- t["No_Recid","No_Recid"]
  FP <- t["Yes_Recid","No_Recid"]
  FN <- t["No_Recid","Yes_Recid"]
  TP <- t["Yes_Recid","Yes_Recid"]
  
  FPR <- FP/(FP+TN)
  FNR <- FN/(FN+TP)
  Error <- 1-cm$overall["Accuracy"]
  
  c(Error=Error, FPR=FPR, FNR=FNR)
}
cat("\nCaucasian:\n")

Caucasian:
print(metric_fun(subset(df_test, Race=="Caucasian")))
Error.Accuracy            FPR            FNR 
     0.4063927      0.2840467      0.5801105 
cat("\nAfrican-American:\n")

African-American:
print(metric_fun(subset(df_test, Race=="African-American")))
Error.Accuracy            FPR            FNR 
     0.3841167      0.4349442      0.3448276 
# =============================
# Q2: Random Forest + LogitBoost
# =============================
cat("\n--- Q2: Model Tuning ---\n")

--- Q2: Model Tuning ---
cv5 <- trainControl(method="cv", number=5, classProbs=TRUE)
# -----------------------------
# Random Forest Tuning
# -----------------------------
cat("\n--- Q2: Random Forest ---\n")

--- Q2: Random Forest ---
rf_grid <- expand.grid(mtry = 1:5)
set.seed(123)
rf_model <- train(
  formula_all,
  data=train,
  method="rf",
  trControl=cv5,
  tuneGrid=rf_grid,
  ntree=500
)
print(rf_model)
Random Forest 

4223 samples
   5 predictor
   2 classes: 'No_Recid', 'Yes_Recid' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 3378, 3379, 3378, 3378, 3379 
Resampling results across tuning parameters:

  mtry  Accuracy   Kappa    
  1     0.6649275  0.3298702
  2     0.6810295  0.3621589
  3     0.6637432  0.3275372
  4     0.6438543  0.2876618
  5     0.6367495  0.2733942

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was mtry = 2.
# Predictions
rf_probs <- predict(rf_model, test, type="prob")
rf_pred <- ifelse(rf_probs$Yes_Recid>0.5,"Yes_Recid","No_Recid")
rf_df <- data.frame(
  Actual=test$is_recid,
  Predicted=rf_pred,
  Race=test$race
)
cat("\nRF Overall:\n")

RF Overall:
print(metric_fun(rf_df))
Error.Accuracy            FPR            FNR 
     0.3213270      0.2946768      0.3478261 
cat("\nRF Caucasian:\n")

RF Caucasian:
print(metric_fun(subset(rf_df, Race=="Caucasian")))
Error.Accuracy            FPR            FNR 
     0.3424658      0.2140078      0.5248619 
cat("\nRF African-American:\n")

RF African-American:
print(metric_fun(subset(rf_df, Race=="African-American")))
Error.Accuracy            FPR            FNR 
     0.3063209      0.3717472      0.2557471 
# -----------------------------
# LogitBoost Tuning
# -----------------------------
cat("\n--- Q2: LogitBoost ---\n")

--- Q2: LogitBoost ---
grid_logit <- expand.grid(nIter=c(50,100,150,200,250,300))
set.seed(123)
logit_model <- train(
  formula_all,
  data=train,
  method="LogitBoost",
  trControl=cv5,
  tuneGrid=grid_logit
)
print(logit_model)
Boosted Logistic Regression 

4223 samples
   5 predictor
   2 classes: 'No_Recid', 'Yes_Recid' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 3378, 3379, 3378, 3378, 3379 
Resampling results across tuning parameters:

  nIter  Accuracy   Kappa    
   50    0.7638984  0.4223580
  100    0.7583055  0.5060439
  150    0.7638984  0.4223580
  200    0.7583055  0.5060439
  250    0.7638984  0.4223580
  300    0.7583055  0.5060439

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was nIter = 50.
# Predictions
lb_probs <- predict(logit_model, test, type="prob")
lb_pred <- ifelse(lb_probs$Yes_Recid>0.5,"Yes_Recid","No_Recid")
lb_df <- data.frame(
  Actual=test$is_recid,
  Predicted=lb_pred,
  Race=test$race
)
cat("\nLogitBoost Overall:\n")

LogitBoost Overall:
print(metric_fun(lb_df))
Error.Accuracy            FPR            FNR 
    0.40379147     0.08174905     0.72400756 
cat("\nLogitBoost Caucasian:\n")

LogitBoost Caucasian:
print(metric_fun(subset(lb_df, Race=="Caucasian")))
Error.Accuracy            FPR            FNR 
    0.37442922     0.05447471     0.82872928 
cat("\nLogitBoost African-American:\n")

LogitBoost African-American:
print(metric_fun(subset(lb_df, Race=="African-American")))
Error.Accuracy            FPR            FNR 
     0.4246353      0.1078067      0.6695402 
cat("\n\n=============================\nEND OF FULL SCRIPT\n=============================\n")


=============================
END OF FULL SCRIPT
=============================

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiANCg0KVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqLiANCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMgRklOQUwgRlVMTCBTT0xVVElPTiDigJQgQ09NUExFVEUgQ09ERSBGT1IgQUxMIFFVRVNUSU9OUyAoUTHigJNRMikNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojIExvYWQgUmVxdWlyZWQgUGFja2FnZXMNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCnBrZ3MgPC0gYygiY2FyZXQiLCAiaXByZWQiLCAicnBhcnQucGxvdCIsICJyYW5kb21Gb3Jlc3QiLCAiY2FUb29scyIpDQpuZXdfcGtncyA8LSBwa2dzWyFwa2dzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl1dDQppZiAobGVuZ3RoKG5ld19wa2dzKSA+IDApIGluc3RhbGwucGFja2FnZXMobmV3X3BrZ3MpDQoNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGlwcmVkKQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpsaWJyYXJ5KGNhVG9vbHMpDQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgUTEoYSk6IExvYWQgYW5kIFByZXBhcmUgRGF0YQ0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KY2F0KCJcbi0tLSBRMShhKTogTG9hZGluZyBEYXRhIC0tLVxuIikNCg0KY3N2X3BhdGggPC0gIkM6XFxVc2Vyc1xcTGVub3ZvXFxPbmVEcml2ZVxcRGVza3RvcFxcZmFsbCAyMDI1XFxzdGF0XFxBc3NpZ25tZW50IDQtU3RhdFxcY29tcGFzLmNzdiINCg0KaWYgKCFmaWxlLmV4aXN0cyhjc3ZfcGF0aCkpIHsNCiAgc3RvcCgiRVJST1I6IGNvbXBhcy5jc3YgaXMgTk9UIGluIHdvcmtpbmcgZGlyZWN0b3J5OiAiLCBnZXR3ZCgpKQ0KfQ0KDQpjb21wYXNfZGF0YSA8LSByZWFkLmNzdihjc3ZfcGF0aCkNCmNhdCgiTG9hZGVkIGNvbXBhcy5jc3Ygd2l0aCIsIG5yb3coY29tcGFzX2RhdGEpLCAicm93cy5cbiIpDQoNCiMgRXhwZWN0ZWQgZmllbGRzOg0KcmVxdWlyZWRfY29scyA8LSBjKCJpc19yZWNpZCIsInJhY2UiLCJzZXgiLCJhZ2UiLA0KICAgICAgICAgICAgICAgICAgICJwcmlvcnNfY291bnQiLCJjX2NoYXJnZV9kZWdyZWUiKQ0KDQptaXNzaW5nIDwtIHNldGRpZmYocmVxdWlyZWRfY29scywgbmFtZXMoY29tcGFzX2RhdGEpKQ0KaWYgKGxlbmd0aChtaXNzaW5nKT4wKSB7DQogIHN0b3AoIk1pc3NpbmcgY29sdW1ucyBpbiBjb21wYXMuY3N2OiAiLCBwYXN0ZShtaXNzaW5nLCBjb2xsYXBzZT0iLCAiKSkNCn0NCg0KIyBGaWx0ZXIgdHdvIHJhY2VzIG9ubHkNCmZpbHRlcmVkIDwtIHN1YnNldChjb21wYXNfZGF0YSwNCiAgICAgICAgICAgICAgICAgICByYWNlICVpbiUgYygiQWZyaWNhbi1BbWVyaWNhbiIsICJDYXVjYXNpYW4iKSkNCg0KIyBGYWN0b3IgY29udmVyc2lvbnMNCmZpbHRlcmVkJGlzX3JlY2lkIDwtIGZhY3RvcihmaWx0ZXJlZCRpc19yZWNpZCkNCmxldmVscyhmaWx0ZXJlZCRpc19yZWNpZCkgPC0gYygiTm9fUmVjaWQiLCJZZXNfUmVjaWQiKQ0KDQpmaWx0ZXJlZCRyYWNlICA8LSBmYWN0b3IoZmlsdGVyZWQkcmFjZSkNCmZpbHRlcmVkJHNleCAgIDwtIGZhY3RvcihmaWx0ZXJlZCRzZXgpDQpmaWx0ZXJlZCRjX2NoYXJnZV9kZWdyZWUgPC0gZmFjdG9yKGZpbHRlcmVkJGNfY2hhcmdlX2RlZ3JlZSkNCg0KIyBTYXZlIGZhY3RvciBsZXZlbHMgZm9yIFExKGQpDQphbGxfcmFjZXMgICA8LSBsZXZlbHMoZmlsdGVyZWQkcmFjZSkNCmFsbF9zZXhlcyAgIDwtIGxldmVscyhmaWx0ZXJlZCRzZXgpDQphbGxfY2hhcmdlcyA8LSBsZXZlbHMoZmlsdGVyZWQkY19jaGFyZ2VfZGVncmVlKQ0KDQojIFNwbGl0IGRhdGENCnNldC5zZWVkKDEyMykNCmlkeCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGZpbHRlcmVkJGlzX3JlY2lkLCBwPTAuOCwgbGlzdD1GQUxTRSkNCnRyYWluIDwtIGZpbHRlcmVkW2lkeCwgXQ0KdGVzdCAgPC0gZmlsdGVyZWRbLWlkeCwgXQ0KDQpjYXQoIlRyYWluaW5nIHJvd3M6IiwgbnJvdyh0cmFpbiksICJUZXN0aW5nIHJvd3M6IiwgbnJvdyh0ZXN0KSwgIlxuIikNCg0KDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojIFExKGIpOiBCYWdnaW5nIE1vZGVsDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQpjYXQoIlxuLS0tIFExKGIpOiBCYWdnaW5nIE1vZGVsIC0tLVxuIikNCg0KZm9ybXVsYV9hbGwgPC0gaXNfcmVjaWQgfiByYWNlICsgc2V4ICsgYWdlICsgcHJpb3JzX2NvdW50ICsgY19jaGFyZ2VfZGVncmVlDQoNCmN0cmxfYmFnIDwtIHRyYWluQ29udHJvbChtZXRob2Q9Im5vbmUiLCBjbGFzc1Byb2JzPVRSVUUpDQoNCnNldC5zZWVkKDEyMykNCmJhZ19tb2RlbCA8LSB0cmFpbigNCiAgZm9ybSA9IGZvcm11bGFfYWxsLA0KICBkYXRhID0gdHJhaW4sDQogIG1ldGhvZCA9ICJ0cmVlYmFnIiwNCiAgbmJhZ2cgPSA1MCwNCiAgdHJDb250cm9sID0gY3RybF9iYWcNCikNCg0KcHJpbnQoYmFnX21vZGVsKQ0KDQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgUTEoYyk6IFBsb3QgMyBCYWdnZWQgVHJlZXMgLSBDT1JSRUNURUQNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmNhdCgiXG4tLS0gUTEoYyk6IFNhdmluZyBGaXJzdCAzIFRyZWVzIC0tLVxuIikNCg0KIyBCdWlsZCBpcHJlZCB2ZXJzaW9uIGZvciB0cmVlIGV4dHJhY3Rpb24NCnNldC5zZWVkKDEyMykNCmJhZ19pcHJlZCA8LSBpcHJlZDo6YmFnZ2luZyhmb3JtdWxhX2FsbCwgZGF0YSA9IHRyYWluLCBuYmFnZyA9IDUwKQ0KDQpzYXZlX3RyZWUgPC0gZnVuY3Rpb24odHJlZV9vYmosIG5hbWUpIHsNCiAgb3V0IDwtIGZpbGUucGF0aChnZXR3ZCgpLCBuYW1lKQ0KICANCiAgdHJ5Q2F0Y2goew0KICAgIHBuZyhvdXQsIHdpZHRoID0gMTAwMCwgaGVpZ2h0ID0gODAwKQ0KICAgICMgRXh0cmFjdCB0aGUgcnBhcnQgb2JqZWN0IGZyb20gdGhlIGJhZ2dpbmcgdHJlZQ0KICAgIHJwYXJ0X29iaiA8LSB0cmVlX29iaiRidHJlZQ0KICAgIHJwYXJ0LnBsb3QocnBhcnRfb2JqLCBtYWluID0gbmFtZSwgcm91bmRpbnQgPSBGQUxTRSkNCiAgICBkZXYub2ZmKCkNCiAgICBjYXQoIlNhdmVkIiwgbmFtZSwgIlxuIikNCiAgfSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7DQogICAgY2F0KCJFcnJvciBzYXZpbmciLCBuYW1lLCAiOiIsIGUkbWVzc2FnZSwgIlxuIikNCiAgICAjIFRyeSB0byBwbG90IG9uIHNjcmVlbiBhcyBmYWxsYmFjaw0KICAgIHRyeSh7DQogICAgICBycGFydF9vYmogPC0gdHJlZV9vYmokYnRyZWUNCiAgICAgIHJwYXJ0LnBsb3QocnBhcnRfb2JqLCBtYWluID0gbmFtZSkNCiAgICB9LCBzaWxlbnQgPSBUUlVFKQ0KICB9KQ0KfQ0KDQojIFBsb3QgZmlyc3QgMyB0cmVlcw0KZm9yIChpIGluIDE6Mykgew0KICB0cmVlX25hbWUgPC0gcGFzdGUwKCJxMWNfdHJlZV8iLCBpLCAiLnBuZyIpDQogIHNhdmVfdHJlZShiYWdfaXByZWQkbXRyZWVzW1tpXV0sIHRyZWVfbmFtZSkNCn0NCg0KDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojIFExKGQpOiBQcmVkaWN0IE5ldyBJbmRpdmlkdWFscw0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KY2F0KCJcbi0tLSBRMShkKTogTmV3IEFycmVzdGVlIFByZWRpY3Rpb25zIC0tLVxuIikNCg0KbmV3X3Blb3BsZSA8LSBkYXRhLmZyYW1lKA0KICByYWNlID0gZmFjdG9yKGMoIkNhdWNhc2lhbiIsIkNhdWNhc2lhbiIsIkFmcmljYW4tQW1lcmljYW4iKSwNCiAgICAgICAgICAgICAgICBsZXZlbHMgPSBhbGxfcmFjZXMpLA0KICBzZXggID0gZmFjdG9yKGMoIk1hbGUiLCJGZW1hbGUiLCJNYWxlIiksIGxldmVscyA9IGFsbF9zZXhlcyksDQogIGFnZSAgPSBjKDE5LDIyLDM0KSwNCiAgcHJpb3JzX2NvdW50ID0gYygxLDIsMCksDQogIGNfY2hhcmdlX2RlZ3JlZSA9IGZhY3RvcihjKCJGIiwiTSIsIk0iKSwgbGV2ZWxzID0gYWxsX2NoYXJnZXMpDQopDQoNCnByb2JzX25ldyA8LSBwcmVkaWN0KGJhZ19tb2RlbCwgbmV3X3Blb3BsZSwgdHlwZT0icHJvYiIpDQoNCnJlc3VsdHNfbmV3IDwtIGRhdGEuZnJhbWUoDQogIFBlcnNvbj1jKCJBcmNoaWUiLCJCZXR0eSIsIkNodWNrIiksDQogIFByb2JhYmlsaXR5PXByb2JzX25ldyRZZXNfUmVjaWQNCikNCg0KcHJpbnQocmVzdWx0c19uZXcpDQoNCg0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyBRMShlKTogRmFpcm5lc3MgTWV0cmljcw0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KY2F0KCJcbi0tLSBRMShlKTogRmFpcm5lc3MgTWV0cmljcyAtLS1cbiIpDQoNCnByb2JzX3Rlc3QgPC0gcHJlZGljdChiYWdfbW9kZWwsIHRlc3QsIHR5cGU9InByb2IiKQ0KcHJlZF90ZXN0IDwtIGlmZWxzZShwcm9ic190ZXN0JFllc19SZWNpZD4wLjUsIlllc19SZWNpZCIsIk5vX1JlY2lkIikNCg0KZGZfdGVzdCA8LSBkYXRhLmZyYW1lKA0KICBBY3R1YWw9dGVzdCRpc19yZWNpZCwNCiAgUHJlZGljdGVkPXByZWRfdGVzdCwNCiAgUmFjZT10ZXN0JHJhY2UNCikNCg0KbWV0cmljX2Z1biA8LSBmdW5jdGlvbihkZil7DQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeCgNCiAgICBmYWN0b3IoZGYkUHJlZGljdGVkLCBsZXZlbHM9YygiTm9fUmVjaWQiLCJZZXNfUmVjaWQiKSksDQogICAgZmFjdG9yKGRmJEFjdHVhbCwgbGV2ZWxzPWMoIk5vX1JlY2lkIiwiWWVzX1JlY2lkIikpLA0KICAgIHBvc2l0aXZlPSJZZXNfUmVjaWQiDQogICkNCiAgdCA8LSBjbSR0YWJsZQ0KICBUTiA8LSB0WyJOb19SZWNpZCIsIk5vX1JlY2lkIl0NCiAgRlAgPC0gdFsiWWVzX1JlY2lkIiwiTm9fUmVjaWQiXQ0KICBGTiA8LSB0WyJOb19SZWNpZCIsIlllc19SZWNpZCJdDQogIFRQIDwtIHRbIlllc19SZWNpZCIsIlllc19SZWNpZCJdDQogIA0KICBGUFIgPC0gRlAvKEZQK1ROKQ0KICBGTlIgPC0gRk4vKEZOK1RQKQ0KICBFcnJvciA8LSAxLWNtJG92ZXJhbGxbIkFjY3VyYWN5Il0NCiAgDQogIGMoRXJyb3I9RXJyb3IsIEZQUj1GUFIsIEZOUj1GTlIpDQp9DQoNCmNhdCgiXG5DYXVjYXNpYW46XG4iKQ0KcHJpbnQobWV0cmljX2Z1bihzdWJzZXQoZGZfdGVzdCwgUmFjZT09IkNhdWNhc2lhbiIpKSkNCg0KY2F0KCJcbkFmcmljYW4tQW1lcmljYW46XG4iKQ0KcHJpbnQobWV0cmljX2Z1bihzdWJzZXQoZGZfdGVzdCwgUmFjZT09IkFmcmljYW4tQW1lcmljYW4iKSkpDQoNCg0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyBRMjogUmFuZG9tIEZvcmVzdCArIExvZ2l0Qm9vc3QNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmNhdCgiXG4tLS0gUTI6IE1vZGVsIFR1bmluZyAtLS1cbiIpDQoNCmN2NSA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj01LCBjbGFzc1Byb2JzPVRSVUUpDQoNCg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBSYW5kb20gRm9yZXN0IFR1bmluZw0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KY2F0KCJcbi0tLSBRMjogUmFuZG9tIEZvcmVzdCAtLS1cbiIpDQoNCnJmX2dyaWQgPC0gZXhwYW5kLmdyaWQobXRyeSA9IDE6NSkNCg0Kc2V0LnNlZWQoMTIzKQ0KcmZfbW9kZWwgPC0gdHJhaW4oDQogIGZvcm11bGFfYWxsLA0KICBkYXRhPXRyYWluLA0KICBtZXRob2Q9InJmIiwNCiAgdHJDb250cm9sPWN2NSwNCiAgdHVuZUdyaWQ9cmZfZ3JpZCwNCiAgbnRyZWU9NTAwDQopDQoNCnByaW50KHJmX21vZGVsKQ0KDQojIFByZWRpY3Rpb25zDQpyZl9wcm9icyA8LSBwcmVkaWN0KHJmX21vZGVsLCB0ZXN0LCB0eXBlPSJwcm9iIikNCnJmX3ByZWQgPC0gaWZlbHNlKHJmX3Byb2JzJFllc19SZWNpZD4wLjUsIlllc19SZWNpZCIsIk5vX1JlY2lkIikNCg0KcmZfZGYgPC0gZGF0YS5mcmFtZSgNCiAgQWN0dWFsPXRlc3QkaXNfcmVjaWQsDQogIFByZWRpY3RlZD1yZl9wcmVkLA0KICBSYWNlPXRlc3QkcmFjZQ0KKQ0KDQpjYXQoIlxuUkYgT3ZlcmFsbDpcbiIpDQpwcmludChtZXRyaWNfZnVuKHJmX2RmKSkNCg0KY2F0KCJcblJGIENhdWNhc2lhbjpcbiIpDQpwcmludChtZXRyaWNfZnVuKHN1YnNldChyZl9kZiwgUmFjZT09IkNhdWNhc2lhbiIpKSkNCg0KY2F0KCJcblJGIEFmcmljYW4tQW1lcmljYW46XG4iKQ0KcHJpbnQobWV0cmljX2Z1bihzdWJzZXQocmZfZGYsIFJhY2U9PSJBZnJpY2FuLUFtZXJpY2FuIikpKQ0KDQoNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgTG9naXRCb29zdCBUdW5pbmcNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmNhdCgiXG4tLS0gUTI6IExvZ2l0Qm9vc3QgLS0tXG4iKQ0KDQpncmlkX2xvZ2l0IDwtIGV4cGFuZC5ncmlkKG5JdGVyPWMoNTAsMTAwLDE1MCwyMDAsMjUwLDMwMCkpDQoNCnNldC5zZWVkKDEyMykNCmxvZ2l0X21vZGVsIDwtIHRyYWluKA0KICBmb3JtdWxhX2FsbCwNCiAgZGF0YT10cmFpbiwNCiAgbWV0aG9kPSJMb2dpdEJvb3N0IiwNCiAgdHJDb250cm9sPWN2NSwNCiAgdHVuZUdyaWQ9Z3JpZF9sb2dpdA0KKQ0KDQpwcmludChsb2dpdF9tb2RlbCkNCg0KIyBQcmVkaWN0aW9ucw0KbGJfcHJvYnMgPC0gcHJlZGljdChsb2dpdF9tb2RlbCwgdGVzdCwgdHlwZT0icHJvYiIpDQpsYl9wcmVkIDwtIGlmZWxzZShsYl9wcm9icyRZZXNfUmVjaWQ+MC41LCJZZXNfUmVjaWQiLCJOb19SZWNpZCIpDQoNCmxiX2RmIDwtIGRhdGEuZnJhbWUoDQogIEFjdHVhbD10ZXN0JGlzX3JlY2lkLA0KICBQcmVkaWN0ZWQ9bGJfcHJlZCwNCiAgUmFjZT10ZXN0JHJhY2UNCikNCg0KY2F0KCJcbkxvZ2l0Qm9vc3QgT3ZlcmFsbDpcbiIpDQpwcmludChtZXRyaWNfZnVuKGxiX2RmKSkNCg0KY2F0KCJcbkxvZ2l0Qm9vc3QgQ2F1Y2FzaWFuOlxuIikNCnByaW50KG1ldHJpY19mdW4oc3Vic2V0KGxiX2RmLCBSYWNlPT0iQ2F1Y2FzaWFuIikpKQ0KDQpjYXQoIlxuTG9naXRCb29zdCBBZnJpY2FuLUFtZXJpY2FuOlxuIikNCnByaW50KG1ldHJpY19mdW4oc3Vic2V0KGxiX2RmLCBSYWNlPT0iQWZyaWNhbi1BbWVyaWNhbiIpKSkNCg0KY2F0KCJcblxuPT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbkVORCBPRiBGVUxMIFNDUklQVFxuPT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiIpDQoNCmBgYA0KDQpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSouDQoNCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ3RybCtTaGlmdCtLKiB0byBwcmV2aWV3IHRoZSBIVE1MIGZpbGUpLg0KDQpUaGUgcHJldmlldyBzaG93cyB5b3UgYSByZW5kZXJlZCBIVE1MIGNvcHkgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBlZGl0b3IuIENvbnNlcXVlbnRseSwgdW5saWtlICpLbml0KiwgKlByZXZpZXcqIGRvZXMgbm90IHJ1biBhbnkgUiBjb2RlIGNodW5rcy4gSW5zdGVhZCwgdGhlIG91dHB1dCBvZiB0aGUgY2h1bmsgd2hlbiBpdCB3YXMgbGFzdCBydW4gaW4gdGhlIGVkaXRvciBpcyBkaXNwbGF5ZWQuDQo=