00 Setup & reproducibility
library(tidyverse)
library(bnlearn)
library(caret)
library(pROC)
library(mice)
library(recipes)
library(themis)
# One global seed governs the whole single-split pipeline.
GLOBAL_SEED <- 42
set.seed(GLOBAL_SEED)
01 Load & clean data
stroke_data <- read.csv("healthcare-dataset-stroke-data.csv")
stroke_data <- stroke_data %>%
filter(gender != "Other") %>% # single ambiguous record
droplevels() %>%
mutate(
bmi = as.numeric(as.character(bmi)), # "N/A" strings -> NA
stroke = as.factor(stroke),
hypertension = as.factor(hypertension),
heart_disease = as.factor(heart_disease),
gender = as.factor(gender),
ever_married = as.factor(ever_married),
work_type = as.factor(work_type),
Residence_type = as.factor(Residence_type),
smoking_status = as.factor(smoking_status)
)
cat(sprintf("N = %d patients | stroke prevalence = %.1f%%\n",
nrow(stroke_data), 100 * mean(stroke_data$stroke == "1")))
N = 5109 patients | stroke prevalence = 4.9%
03 Stratified 60/20/20 split
# Stratified split that preserves the (rare) stroke prevalence in each fold.
# A `seed` argument is exposed so the SAME function can drive the repeated-split
# robustness analysis in Segment 14.
split_stroke <- function(data, seed = GLOBAL_SEED, p_train = 0.6, p_val = 0.2) {
set.seed(seed)
s0 <- data %>% filter(stroke == "0"); s1 <- data %>% filter(stroke == "1")
n0 <- nrow(s0); n1 <- nrow(s1)
tr0 <- sample(n0, round(n0 * p_train)); tr1 <- sample(n1, round(n1 * p_train))
va0 <- sample(setdiff(seq_len(n0), tr0), round(n0 * p_val))
va1 <- sample(setdiff(seq_len(n1), tr1), round(n1 * p_val))
list(
train = bind_rows(s0[tr0, ], s1[tr1, ]),
val = bind_rows(s0[va0, ], s1[va1, ]),
test = bind_rows(s0[setdiff(seq_len(n0), c(tr0, va0)), ],
s1[setdiff(seq_len(n1), c(tr1, va1)), ])
)
}
splits <- split_stroke(stroke_data, seed = GLOBAL_SEED)
trainset <- splits$train; valset <- splits$val; testset <- splits$test
for (nm in c("trainset", "valset", "testset")) {
d <- get(nm); p <- prop.table(table(d$stroke))
cat(sprintf("%-9s n=%4d | stroke=%.3f\n", nm, nrow(d), p["1"]))
}
trainset n=3065 | stroke=0.049
valset n=1022 | stroke=0.049
testset n=1022 | stroke=0.049
04 Preprocessing helpers
# --- (a) Discretising imputer: median BMI from TRAIN only, then WHO/CDC/ADA bins
impute_baseline <- function(data, train_median_bmi) {
data %>%
mutate(bmi = as.numeric(as.character(bmi)),
bmi = ifelse(is.na(bmi), train_median_bmi, bmi)) %>%
mutate(
age = cut(age, breaks = c(seq(0, 85, by = 5), Inf), right = FALSE,
labels = c("0-4","5-9","10-14","15-19","20-24","25-29","30-34",
"35-39","40-44","45-49","50-54","55-59","60-64","65-69",
"70-74","75-79","80-84","85+")),
bmi = cut(bmi, breaks = c(0, 18.5, 25, 30, Inf), right = FALSE,
labels = c("Underweight","Normal","Overweight","Obese")),
avg_glucose_level = cut(avg_glucose_level, breaks = c(0, 100, 126, Inf),
right = FALSE, labels = c("Normal","Prediabetes","Diabetes"))
) %>%
mutate(across(c(gender,hypertension,heart_disease,ever_married,
work_type,Residence_type,smoking_status,stroke), as.factor)) %>%
droplevels() %>% select(-any_of("id"))
}
# --- (b) Continuous/hybrid prep: keep age/bmi/glucose numeric
prep_continuous <- function(df, train_median_bmi) {
df %>%
mutate(bmi = as.numeric(as.character(bmi)),
bmi = ifelse(is.na(bmi), train_median_bmi, bmi),
avg_glucose_level = as.numeric(avg_glucose_level),
age = as.numeric(age)) %>%
mutate(across(c(gender,hypertension,heart_disease,ever_married,
work_type,Residence_type,smoking_status,stroke), as.factor)) %>%
select(-any_of("id"))
}
# --- (c) SMOTENC synthetic oversampling for mixed (categorical+continuous) data
apply_smotenc <- function(df, target_var = "stroke") {
df <- droplevels(as.data.frame(df))
rec <- recipe(formula(paste(target_var, "~ .")), data = df) %>%
step_smotenc(all_outcomes(), over_ratio = 1, seed = GLOBAL_SEED) %>% prep()
out <- juice(rec) %>%
mutate(across(where(is.numeric), as.numeric),
across(where(is.integer), as.numeric),
across(where(is.character), as.factor))
droplevels(as.data.frame(out))
}
05 Build the 8 ablation arms (M1–M8)
# Leakage-safe MICE: combine the folds but `ignore` everything except TRAIN rows,
# so imputation rules are learned from training data only.
trainset$split <- "train"; valset$split <- "val"; testset$split <- "test"
combined <- bind_rows(trainset, valset, testset) %>% select(-any_of("id"))
mice_safe <- suppressWarnings(
mice(combined, m = 1, ignore = combined$split != "train",
maxit = 5, seed = GLOBAL_SEED, printFlag = FALSE))
done <- complete(mice_safe, 1)
train_m2_raw <- done %>% filter(split=="train") %>% select(-split)
val_m2_raw <- done %>% filter(split=="val") %>% select(-split)
test_m2_raw <- done %>% filter(split=="test") %>% select(-split)
trainset <- trainset %>% select(-split)
valset <- valset %>% select(-split)
testset <- testset %>% select(-split)
med <- median(as.numeric(as.character(trainset$bmi)), na.rm = TRUE) # TRAIN median
# Factorial 2x2x2: {discrete|continuous} x {median|MICE} x {none|SMOTE}
train_m1 <- impute_baseline(trainset, med); val_m1 <- impute_baseline(valset, med); test_m1 <- impute_baseline(testset, med) # discrete/median/none
train_m2 <- impute_baseline(train_m2_raw, med); val_m2 <- impute_baseline(val_m2_raw, med); test_m2 <- impute_baseline(test_m2_raw, med) # discrete/MICE/none
train_m3 <- prep_continuous(trainset, med); val_m3 <- prep_continuous(valset, med); test_m3 <- prep_continuous(testset, med) # continuous/median/none
train_m4 <- apply_smotenc(train_m1); val_m4 <- val_m1; test_m4 <- test_m1 # discrete/median/SMOTE
train_m5_base <- prep_continuous(train_m2_raw, med)
train_m5 <- apply_smotenc(train_m5_base); val_m5 <- prep_continuous(val_m2_raw, med); test_m5 <- prep_continuous(test_m2_raw, med) # continuous/MICE/SMOTE
train_m6 <- train_m5_base; val_m6 <- val_m5; test_m6 <- test_m5 # continuous/MICE/none
train_m7 <- apply_smotenc(train_m3); val_m7 <- val_m3; test_m7 <- test_m3 # continuous/median/SMOTE
train_m8 <- apply_smotenc(train_m2); val_m8 <- val_m1; test_m8 <- test_m1 # discrete/MICE/SMOTE
cat("Arms M1-M8 built.\n")
Arms M1-M8 built.
06 Feature association screen
# Cramer's V (categorical), point-biserial (continuous) vs stroke; used to
# justify which edges are whitelisted/blacklisted in Segment 07.
suppressPackageStartupMessages({library(vcd)})
cat_vars <- c("gender","hypertension","heart_disease","ever_married",
"work_type","Residence_type","smoking_status")
num_vars <- c("age","avg_glucose_level","bmi")
cat_tbl <- map_dfr(cat_vars, function(v){
tab <- table(stroke_data[[v]], stroke_data$stroke)
data.frame(Variable=v, Assoc=round(assocstats(tab)$cramer,4),
P=chisq.test(tab)$p.value)})
num_tbl <- map_dfr(num_vars, function(v){
data.frame(Variable=v,
Assoc=round(cor(stroke_data[[v]], as.numeric(as.character(stroke_data$stroke)),
use="complete.obs"),4),
P=t.test(stroke_data[[v]]~stroke_data$stroke)$p.value)})
print(bind_rows(cat_tbl, num_tbl) %>% arrange(desc(abs(Assoc))))
Variable Assoc P
1 age 0.2452 2.175773e-95
2 heart_disease 0.1349 2.120831e-21
3 avg_glucose_level 0.1320 2.373124e-11
4 hypertension 0.1279 1.688936e-19
5 ever_married 0.1083 1.686286e-14
6 work_type 0.0981 5.409035e-10
7 smoking_status 0.0756 2.007704e-06
8 bmi 0.0423 3.377378e-04
9 Residence_type 0.0154 2.998252e-01
10 gender 0.0091 5.598278e-01
07 Structural constraints (whitelist / blacklist)
all_nodes <- c("age","gender","hypertension","heart_disease","ever_married",
"work_type","Residence_type","avg_glucose_level","bmi",
"smoking_status","stroke")
discrete_nodes <- setdiff(all_nodes, c("age","avg_glucose_level","bmi","stroke"))
# Discrete models: clinically supported predictors point INTO stroke.
wl_discrete <- matrix(c("age","stroke","heart_disease","stroke",
"hypertension","stroke","avg_glucose_level","stroke"),
ncol=2, byrow=TRUE, dimnames=list(NULL,c("from","to")))
bl_discrete <- bind_rows(
expand.grid(from=all_nodes, to=c("age","gender"), stringsAsFactors=FALSE) %>% filter(from!=to),
expand.grid(from="stroke", to=all_nodes, stringsAsFactors=FALSE) %>% filter(to!="stroke"),
data.frame(from=c("gender","Residence_type"), to=c("stroke","stroke"))
) %>% as.matrix(); colnames(bl_discrete) <- c("from","to")
# Hybrid models: CGBN forbids continuous->discrete, so age/glucose arcs reverse.
wl_hybrid <- matrix(c("stroke","age","heart_disease","stroke",
"hypertension","stroke","stroke","avg_glucose_level"),
ncol=2, byrow=TRUE, dimnames=list(NULL,c("from","to")))
bl_hybrid <- bind_rows(
expand.grid(from=all_nodes, to="gender", stringsAsFactors=FALSE) %>% filter(from!=to),
expand.grid(from="stroke", to=discrete_nodes, stringsAsFactors=FALSE) %>% filter(to!="stroke"),
data.frame(from=c("gender","Residence_type"), to=c("stroke","stroke"))
) %>% as.matrix(); colnames(bl_hybrid) <- c("from","to")
cat("Whitelists/blacklists ready.\n")
Whitelists/blacklists ready.
08 Core evaluation function
# Trains one BN arm, tunes the threshold on VALIDATION, scores a blind TEST set.
# Structure learning: hill-climbing (HC). Set boot_R>0 for arc-strength bootstrap.
evaluate_variant <- function(train_df, val_df, test_df, model_name, boot_R = 200) {
is_disc <- all(sapply(train_df, is.factor))
wl <- if (is_disc) wl_discrete else wl_hybrid
bl <- if (is_disc) bl_discrete else bl_hybrid
dag <- hc(train_df, whitelist = wl, blacklist = bl)
arc_str <- if (boot_R > 0)
boot.strength(train_df, R = boot_R, algorithm = "hc",
algorithm.args = list(whitelist = wl, blacklist = bl)) else NULL
fitted <- if (is_disc) bn.fit(dag, train_df, method = "bayes", iss = 10) else bn.fit(dag, train_df)
# Posterior P(stroke=1): exact for discrete, likelihood-weighting for hybrid.
get_probs <- function(td) {
if (is_disc) {
pp <- predict(fitted, node="stroke", data=td, method="bayes-lw", prob=TRUE)
return(attr(pp,"prob")["1",])
}
ev_cols <- setdiff(names(td), "stroke")
vapply(seq_len(nrow(td)), function(i){
p <- suppressWarnings(cpquery(fitted, event=(stroke=="1"),
evidence=as.list(td[i, ev_cols]), method="lw", n=1000))
if (is.na(p)) 0 else p }, numeric(1))
}
val_probs <- get_probs(val_df)
roc_val <- roc(val_df$stroke, val_probs, levels=c("0","1"), quiet=TRUE)
opt_thresh <- coords(roc_val, "best", ret="threshold", best.method="youden")$threshold[1]
if (is.na(opt_thresh)) opt_thresh <- 0.5
test_probs <- get_probs(test_df)
roc_test <- roc(test_df$stroke, test_probs, levels=c("0","1"), quiet=TRUE)
test_preds <- factor(ifelse(test_probs >= opt_thresh, "1", "0"), levels=c("0","1"))
cm <- confusionMatrix(test_preds, test_df$stroke, positive="1")
list(
results = data.frame(Model=model_name, Val_Threshold=round(opt_thresh,4),
Test_AUC=round(as.numeric(auc(roc_test)),4),
Test_Sensitivity=round(cm$byClass["Sensitivity"],4),
Test_Specificity=round(cm$byClass["Specificity"],4),
Test_Youden_J=round(cm$byClass["Sensitivity"]+cm$byClass["Specificity"]-1,4),
Test_F1=round(as.numeric(cm$byClass["F1"]),4)),
roc=roc_test, fit=fitted, structure=dag, arc_strength=arc_str,
confusion_matrix=cm, test_probs=test_probs)
}
09 MAIN RESULT — train & rank the 8 arms
experiments <- list(
list(train=train_m1,val=val_m1,test=test_m1,name="M1: Baseline"),
list(train=train_m2,val=val_m2,test=test_m2,name="M2: MICE + Discretize"),
list(train=train_m3,val=val_m3,test=test_m3,name="M3: Continuous Nodes"),
list(train=train_m4,val=val_m4,test=test_m4,name="M4: SMOTE on Baseline"),
list(train=train_m5,val=val_m5,test=test_m5,name="M5: ALL (MICE + Cont + SMOTE)"),
list(train=train_m6,val=val_m6,test=test_m6,name="M6: MICE + Continuous"),
list(train=train_m7,val=val_m7,test=test_m7,name="M7: Continuous + SMOTE"),
list(train=train_m8,val=val_m8,test=test_m8,name="M8: MICE + SMOTE")
)
ablation_results <- data.frame()
roc_list <- list(); dags_list <- list(); arc_str_list <- list()
cm_list <- list(); fit_list <- list()
for (ex in experiments) {
tryCatch({
r <- evaluate_variant(ex$train, ex$val, ex$test, ex$name, boot_R = 200)
ablation_results <- rbind(ablation_results, r$results)
roc_list[[ex$name]] <- r$roc; dags_list[[ex$name]] <- r$structure
arc_str_list[[ex$name]] <- r$arc_strength; cm_list[[ex$name]] <- r$confusion_matrix
fit_list[[ex$name]] <- r$fit
}, error=function(e) cat(sprintf("Error in %s: %s\n", ex$name, e$message)))
}
# 95% CI for AUC (bootstrap), then RANK BY AUC (primary), Youden's J (secondary).
master_leaderboard <- ablation_results
master_leaderboard$Test_AUC_95_CI <- vapply(master_leaderboard$Model, function(m){
ci <- ci.auc(roc_list[[m]], method="bootstrap", boot.n=2000, quiet=TRUE)
sprintf("[%.4f - %.4f]", ci[1], ci[3]) }, character(1))
master_leaderboard <- master_leaderboard %>%
relocate(Test_AUC_95_CI, .after = Test_AUC) %>%
arrange(desc(Test_AUC), desc(Test_Youden_J)) # <-- AUC-primary ranking
print(master_leaderboard)
Model Val_Threshold Test_AUC Test_AUC_95_CI Test_Sensitivity Test_Specificity
Sensitivity5 M6: MICE + Continuous 0.0442 0.8115 [0.7601 - 0.8566] 0.78 0.6872
Sensitivity2 M3: Continuous Nodes 0.0481 0.8031 [0.7548 - 0.8479] 0.78 0.6944
Sensitivity4 M5: ALL (MICE + Cont + SMOTE) 0.3247 0.7836 [0.7193 - 0.8376] 0.84 0.6481
Sensitivity6 M7: Continuous + SMOTE 0.5985 0.7808 [0.7173 - 0.8422] 0.56 0.7726
Sensitivity1 M2: MICE + Discretize 0.0430 0.7200 [0.6525 - 0.7840] 0.62 0.6893
Sensitivity M1: Baseline 0.0550 0.7176 [0.6466 - 0.7860] 0.56 0.7438
Sensitivity7 M8: MICE + SMOTE 0.5290 0.6921 [0.6150 - 0.7636] 0.52 0.7582
Sensitivity3 M4: SMOTE on Baseline 0.1770 0.6841 [0.6034 - 0.7576] 0.66 0.6091
Test_Youden_J Test_F1
Sensitivity5 0.4672 0.1985
Sensitivity2 0.4744 0.2021
Sensitivity4 0.4881 0.1935
Sensitivity6 0.3326 0.1873
Sensitivity1 0.3093 0.1619
Sensitivity 0.3038 0.1713
Sensitivity7 0.2782 0.1672
Sensitivity3 0.2691 0.1425
write.csv(master_leaderboard, "leaderboard_final.csv", row.names = FALSE)
#DAG structure, CPT and inference table #
============================================================ # SEGMENT
9.5 — MANUSCRIPT ARTIFACTS (DAG, CPTs, examples) # Everything is
extracted from the optimal model M6 and written to # CSV/PDF so the
manuscript tables/figures use exact, reproducible values. #
============================================================
library(bnlearn); library(dplyr)
BEST <- "M6: MICE + Continuous"
net <- fit_list[[BEST]] # fitted hybrid network
dag <- dags_list[[BEST]] # structure only
astr <- arc_str_list[[BEST]] # bootstrap arc strengths
cat("Parents of stroke:", paste(parents(net, "stroke"), collapse=", "), "\n")
Parents of stroke: hypertension, heart_disease, ever_married
cat("Children of stroke:", paste(children(net, "stroke"), collapse=", "), "\n")
Children of stroke: age, avg_glucose_level
9.5a DAG — arc list, bootstrap strengths, and figure
# (i) Arc list with bootstrap support (strength) and direction confidence.
arc_tbl <- astr %>%
filter(strength > 0.50 & direction >= 0.50) %>%
arrange(desc(strength))
print(arc_tbl)
from to strength direction
1 hypertension stroke 1.000 1.0000000
2 heart_disease stroke 1.000 1.0000000
3 ever_married work_type 1.000 0.5300000
4 work_type bmi 1.000 1.0000000
5 work_type smoking_status 1.000 0.5600000
6 stroke age 1.000 1.0000000
7 stroke avg_glucose_level 1.000 1.0000000
8 bmi avg_glucose_level 0.980 0.8010204
9 ever_married avg_glucose_level 0.970 1.0000000
10 age avg_glucose_level 0.960 0.9192708
11 age bmi 0.945 0.5449735
12 ever_married age 0.895 1.0000000
13 ever_married heart_disease 0.875 0.7028571
14 ever_married stroke 0.875 1.0000000
15 work_type hypertension 0.670 0.6604478
16 gender heart_disease 0.530 1.0000000
17 work_type age 0.520 1.0000000
write.csv(arc_tbl, "M6_arc_strengths.csv", row.names = FALSE)
# (ii) Publication DAG with edge thickness ~ bootstrap strength.
pdf("M6_DAG.pdf", width = 9, height = 7)
strength.plot(dag, astr, main = "M6: MICE + Continuous — consensus DAG",
shape = "ellipse")
dev.off()
null device
1
print(dag) # full structure summary (nodes, arcs, parents/children)
Bayesian network learned via Score-based methods
model:
[gender][Residence_type][heart_disease|gender][ever_married|heart_disease][work_type|ever_married]
[hypertension|work_type][smoking_status|work_type][stroke|hypertension:heart_disease:ever_married]
[age|ever_married:work_type:stroke][bmi|age:work_type][avg_glucose_level|age:ever_married:bmi:stroke]
nodes: 11
arcs: 17
undirected arcs: 0
directed arcs: 17
average markov blanket size: 4.00
average neighbourhood size: 3.09
average branching factor: 1.55
learning algorithm: Hill-Climbing
score: BIC (cond. Gauss.)
penalization coefficient: 4.013901
tests used in the learning procedure: 211
optimized: TRUE
cat("Model string:\n", modelstring(dag), "\n\n") # compact one-line DAG
Model string:
[gender][Residence_type][heart_disease|gender][ever_married|heart_disease][work_type|ever_married][hypertension|work_type][smoking_status|work_type][stroke|hypertension:heart_disease:ever_married][age|ever_married:work_type:stroke][bmi|age:work_type][avg_glucose_level|age:ever_married:bmi:stroke]
cat("Arc list (from -> to):\n"); print(arcs(dag)) # every directed edge
Arc list (from -> to):
from to
[1,] "stroke" "age"
[2,] "heart_disease" "stroke"
[3,] "hypertension" "stroke"
[4,] "stroke" "avg_glucose_level"
[5,] "work_type" "age"
[6,] "ever_married" "work_type"
[7,] "work_type" "bmi"
[8,] "work_type" "smoking_status"
[9,] "ever_married" "age"
[10,] "ever_married" "avg_glucose_level"
[11,] "work_type" "hypertension"
[12,] "age" "avg_glucose_level"
[13,] "age" "bmi"
[14,] "bmi" "avg_glucose_level"
[15,] "heart_disease" "ever_married"
[16,] "gender" "heart_disease"
[17,] "ever_married" "stroke"
cat("\nMarkov blanket of stroke:", paste(mb(dag, "stroke"), collapse = ", "), "\n")
Markov blanket of stroke: age, hypertension, heart_disease, ever_married, work_type, avg_glucose_level, bmi
cat("Saved M6_DAG.pdf\n")
Saved M6_DAG.pdf
# (iii) Optional interactive DAG (uncomment if visNetwork is installed)
library(visNetwork)
nodes <- data.frame(id = nodes(dag), label = nodes(dag))
edges <- data.frame(from = arcs(dag)[,1], to = arcs(dag)[,2], arrows = "to")
visNetwork(nodes, edges) %>% visEdges(smooth = FALSE)
9.5b Table 5 — CPT of stroke (discrete parents)
# Stroke is discrete with discrete parents (CGBN forbids continuous parents),
# so its CPT is a clean probability table. We tidy it and flag the key rows.
stroke_cpt <- as.data.frame.table(coef(net$stroke), responseName = "prob")
# Keep P(stroke = 1 | parents) and order by risk.
risk1 <- stroke_cpt %>% filter(stroke == "1") %>%
mutate(prob_pct = round(100 * prob, 2)) %>%
arrange(desc(prob)) %>% select(-stroke)
print(risk1)
hypertension heart_disease ever_married prob prob_pct
1 1 1 No 0.75000000 75.00
2 0 1 No 0.15384615 15.38
3 1 0 No 0.13333333 13.33
4 1 1 Yes 0.13333333 13.33
5 0 1 Yes 0.12962963 12.96
6 1 0 Yes 0.10878661 10.88
7 0 0 Yes 0.05137615 5.14
8 0 0 No 0.01192843 1.19
write.csv(risk1, "M6_CPT_stroke.csv", row.names = FALSE)
# Auto-extract the worked examples the manuscript quotes:
cat("\n--- Worked CPT examples (exact values for the text) ---\n")
--- Worked CPT examples (exact values for the text) ---
cat(sprintf("Highest-risk parent combination: %.2f%%\n", max(risk1$prob_pct)))
Highest-risk parent combination: 75.00%
cat(sprintf("Lowest-risk parent combination: %.2f%%\n", min(risk1$prob_pct)))
Lowest-risk parent combination: 1.19%
# Print the full label of the highest-risk cell so you can describe it precisely:
top <- risk1[which.max(risk1$prob_pct), ]
cat("Highest-risk cell:\n"); print(top)
Highest-risk cell:
hypertension heart_disease ever_married prob prob_pct
1 1 1 No 0.75 75
9.5c Table 6 — continuous children of stroke (Gaussian
parameters)
# For each continuous child of stroke, report the local regression coefficients
# and residual SD per discrete-parent configuration (this is the CG parameterisation).
cont_children <- intersect(children(net, "stroke"),
c("age", "avg_glucose_level", "bmi"))
for (nd in cont_children) {
cat("\n==== Continuous node:", nd, "====\n")
cat("Coefficients (intercept/slopes per parent configuration):\n")
print(net[[nd]]$coefficients)
cat("Residual SD per configuration:\n")
print(net[[nd]]$sd)
# Tidy export
co <- as.data.frame(net[[nd]]$coefficients)
co$term <- rownames(co)
write.csv(co, sprintf("M6_params_%s_coef.csv", nd), row.names = FALSE)
write.csv(data.frame(config = names(net[[nd]]$sd), sd = as.numeric(net[[nd]]$sd)),
sprintf("M6_params_%s_sd.csv", nd), row.names = FALSE)
}
==== Continuous node: age ====
Coefficients (intercept/slopes per parent configuration):
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
(Intercept) 6.732127 NA 35.90164 52.3141 16.8 NA 27.76749 50.43486 48.77273 59.87949 7.66 NA 69.25 68.375 NA NA 71.2
17 18 19
(Intercept) 65.35443 66.4 71.21212
Residual SD per configuration:
0 1 2 3 4 5 6 7 8 9 10
4.450686 NA 15.831093 13.120909 2.573368 NA 13.648362 15.352049 24.172327 15.116798 8.966114
11 12 13 14 15 16 17 18 19
NA 9.878428 9.500000 NA NA 10.261037 12.535118 16.652327 10.270582
==== Continuous node: avg_glucose_level ====
Coefficients (intercept/slopes per parent configuration):
0 1 2 3
(Intercept) 89.55712839 37.2230332 13.3129777 -11.671535
age 0.29773939 0.6970657 0.4969107 1.027348
bmi 0.01582724 1.1373560 2.3704633 2.499728
Residual SD per configuration:
0 1 2 3
31.89516 46.49507 53.83766 59.86714
cat("\nSaved per-node coefficient and SD CSVs.\n")
Saved per-node coefficient and SD CSVs.
9.5d Inference & counterfactual examples (Table for the
text)
# Exact, reproducible probabilities for the clinical personas + intervention.
set.seed(2026); n_sim <- 1e6 # logic sampling
pA <- cpquery(net, event=(stroke=="1"),
evidence=(age<=50 & hypertension=="0" & heart_disease=="0"), n=n_sim)
pB <- cpquery(net, event=(stroke=="1"),
evidence=(age>=65 & hypertension=="1" & heart_disease=="1" & avg_glucose_level>=150), n=n_sim)
pC <- cpquery(net, event=(stroke=="1"),
evidence=(age>=65 & hypertension=="1" & heart_disease=="1" & avg_glucose_level<=100), n=n_sim)
inference_tbl <- data.frame(
Scenario = c("A: Low-risk (age<=50, no HTN, no HD)",
"B: High-risk (age>=65, HTN, HD, glucose>=150)",
"C: B + glycaemic control (glucose<=100)"),
Stroke_Probability_pct = round(100 * c(pA, pB, pC), 2))
print(inference_tbl)
Scenario Stroke_Probability_pct
1 A: Low-risk (age<=50, no HTN, no HD) 0.74
2 B: High-risk (age>=65, HTN, HD, glucose>=150) 43.18
3 C: B + glycaemic control (glucose<=100) 31.55
cat(sprintf("\nAbsolute risk reduction (B -> C): %.2f%% -> %.2f%% (delta = %.2f points)\n",
100*pB, 100*pC, 100*(pB - pC)))
Absolute risk reduction (B -> C): 43.18% -> 31.55% (delta = 11.63 points)
write.csv(inference_tbl, "M6_inference_examples.csv", row.names = FALSE)
# Backward (diagnostic) inference: expected glucose given a stroke occurred.
set.seed(42)
post <- cpdist(net, nodes = "avg_glucose_level",
evidence = list(stroke = factor("1", levels = levels(train_m6$stroke))),
method = "lw", n = 1e5)
cat(sprintf("Expected avg glucose among stroke cases (model): %.2f mg/dL\n",
mean(post$avg_glucose_level, na.rm = TRUE)))
Expected avg glucose among stroke cases (model): 123.59 mg/dL
11 Calibration & Brier score
# Brier score = mean((p - y)^2): lower is better-calibrated. Demonstrates the
# claim that SMOTE distorts probabilities (it should INFLATE the Brier score).
brier <- function(m){
y <- as.numeric(as.character(roc_list[[m]]$response))
p <- roc_list[[m]]$predictor
mean((p - y)^2)
}
brier_tbl <- data.frame(Model=names(roc_list),
Brier=round(vapply(names(roc_list), brier, numeric(1)),4)) %>%
arrange(Brier)
print(brier_tbl); write.csv(brier_tbl, "brier_scores.csv", row.names=FALSE)
Model Brier
M1: Baseline M1: Baseline 0.0506
M2: MICE + Discretize M2: MICE + Discretize 0.0506
M6: MICE + Continuous M6: MICE + Continuous 0.0558
M3: Continuous Nodes M3: Continuous Nodes 0.0563
M4: SMOTE on Baseline M4: SMOTE on Baseline 0.1734
M8: MICE + SMOTE M8: MICE + SMOTE 0.1754
M5: ALL (MICE + Cont + SMOTE) M5: ALL (MICE + Cont + SMOTE) 0.1824
M7: Continuous + SMOTE M7: Continuous + SMOTE 0.1836
# Reliability curve for the four headline arms.
# Tie-robust binning: discrete/SMOTE models produce many identical probabilities,
# so quantile cut-points collapse. We use UNIQUE quantile breaks and fall back to
# binning by distinct probability values when ties are severe.
calib_curve <- function(m, bins=10){
y <- as.numeric(as.character(roc_list[[m]]$response)); p <- roc_list[[m]]$predictor
br <- unique(quantile(p, probs=seq(0,1,length.out=bins+1), na.rm=TRUE))
if (length(br) < 3) { # too many ties -> bin by value
g <- cut(p, breaks=unique(c(-Inf, sort(unique(p)))), include.lowest=TRUE)
} else {
g <- cut(p, breaks=br, include.lowest=TRUE)
}
out <- data.frame(model=m, pred=tapply(p, g, mean), obs=tapply(y, g, mean))
na.omit(out)
}
focus <- intersect(c("M1: Baseline","M3: Continuous Nodes","M6: MICE + Continuous",
"M5: ALL (MICE + Cont + SMOTE)"), names(roc_list))
cal <- bind_rows(lapply(focus, calib_curve))
ggplot(cal, aes(pred, obs, colour=model)) +
geom_abline(slope=1, intercept=0, linetype="dashed", colour="grey50") +
geom_line() + geom_point() +
labs(x="Mean predicted probability", y="Observed stroke fraction",
title="Calibration (reliability) curves") + theme_minimal() -> cal_plot
ggsave("calibration_curves.png", cal_plot, width=7, height=6, dpi=300)
print(cal_plot)

12 Opaque-model benchmark: logistic regression & XGBoost
# Same splits, continuous features. Quantifies how close the interpretable BN
# (AUC ~0.81) comes to standard black-box models — the paper's motivating tension.
suppressPackageStartupMessages({library(xgboost)})
bench_tr <- train_m3; bench_va <- val_m3; bench_te <- test_m3 # continuous, no SMOTE
# --- Logistic regression
lr <- glm(stroke ~ ., data=bench_tr, family=binomial)
lr_te <- predict(lr, bench_te, type="response")
lr_auc <- as.numeric(auc(roc(bench_te$stroke, lr_te, levels=c("0","1"), quiet=TRUE)))
# --- XGBoost (stable xgb.train API; compatible with xgboost 1.x-3.x)
# nrounds is tuned by EARLY STOPPING on the validation fold so the comparison is
# fair (a fixed large nrounds overfits on ~249 events and understates XGBoost).
mm_tr <- model.matrix(stroke ~ . -1, data=bench_tr)
mm_va <- model.matrix(stroke ~ . -1, data=bench_va)
mm_te <- model.matrix(stroke ~ . -1, data=bench_te)
feat <- Reduce(intersect, list(colnames(mm_tr), colnames(mm_va), colnames(mm_te)))
dtr <- xgb.DMatrix(mm_tr[, feat, drop=FALSE], label=as.numeric(as.character(bench_tr$stroke)))
dval<- xgb.DMatrix(mm_va[, feat, drop=FALSE], label=as.numeric(as.character(bench_va$stroke)))
dte <- xgb.DMatrix(mm_te[, feat, drop=FALSE], label=as.numeric(as.character(bench_te$stroke)))
params <- list(objective="binary:logistic", eval_metric="auc",
max_depth=3, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8,
scale_pos_weight=sum(bench_tr$stroke=="0")/sum(bench_tr$stroke=="1"))
set.seed(GLOBAL_SEED)
xgb <- xgb.train(params=params, data=dtr, nrounds=1000,
watchlist=list(val=dval), early_stopping_rounds=25, verbose=0)
xgb_te <- predict(xgb, dte)
xgb_auc <- as.numeric(auc(roc(bench_te$stroke, xgb_te, levels=c("0","1"), quiet=TRUE)))
bench_tbl <- data.frame(
Model=c("Bayesian network (M6)","Logistic regression","XGBoost"),
Test_AUC=round(c(master_leaderboard$Test_AUC[master_leaderboard$Model=="M6: MICE + Continuous"],
lr_auc, xgb_auc),4))
print(bench_tbl); write.csv(bench_tbl, "benchmark_opaque.csv", row.names=FALSE)
Model Test_AUC
1 Bayesian network (M6) 0.8115
2 Logistic regression 0.8188
3 XGBoost 0.8222
13 Hypothesis testing — DeLong (AUC) + McNemar (sensitivity)
# Thresholds are read from the leaderboard (no hard-coding).
thr <- function(m) master_leaderboard$Val_Threshold[master_leaderboard$Model==m]
# McNemar on the actual-stroke cohort: did model A catch cases B missed?
mcnemar_sens <- function(A, B){
y <- roc_list[[A]]$response
pa <- ifelse(roc_list[[A]]$predictor >= thr(A), 1, 0)
pb <- ifelse(roc_list[[B]]$predictor >= thr(B), 1, 0)
idx <- which(y == 1)
tab <- table(A=factor(pa[idx],0:1), B=factor(pb[idx],0:1))
cat(sprintf("\nMcNemar (sensitivity) %s vs %s:\n", A, B)); print(mcnemar.test(tab))
}
cat("=== H1 Topology: continuous vs discrete ===\n")
=== H1 Topology: continuous vs discrete ===
print(roc.test(roc_list[["M3: Continuous Nodes"]], roc_list[["M1: Baseline"]], method="delong"))
DeLong's test for two correlated ROC curves
data: roc_list[["M3: Continuous Nodes"]] and roc_list[["M1: Baseline"]]
Z = 2.2989, p-value = 0.02151
alternative hypothesis: true difference in AUC is not equal to 0
95 percent confidence interval:
0.01261342 0.15849769
sample estimates:
AUC of roc1 AUC of roc2
0.8031070 0.7175514
print(roc.test(roc_list[["M6: MICE + Continuous"]], roc_list[["M2: MICE + Discretize"]], method="delong"))
DeLong's test for two correlated ROC curves
data: roc_list[["M6: MICE + Continuous"]] and roc_list[["M2: MICE + Discretize"]]
Z = 2.546, p-value = 0.0109
alternative hypothesis: true difference in AUC is not equal to 0
95 percent confidence interval:
0.02106229 0.16194183
sample estimates:
AUC of roc1 AUC of roc2
0.8114815 0.7199794
cat("\n=== H2 Imputation: MICE vs median ===\n")
=== H2 Imputation: MICE vs median ===
print(roc.test(roc_list[["M2: MICE + Discretize"]], roc_list[["M1: Baseline"]], method="delong"))
DeLong's test for two correlated ROC curves
data: roc_list[["M2: MICE + Discretize"]] and roc_list[["M1: Baseline"]]
Z = 0.37426, p-value = 0.7082
alternative hypothesis: true difference in AUC is not equal to 0
95 percent confidence interval:
-0.01028730 0.01514327
sample estimates:
AUC of roc1 AUC of roc2
0.7199794 0.7175514
print(roc.test(roc_list[["M6: MICE + Continuous"]], roc_list[["M3: Continuous Nodes"]], method="delong"))
DeLong's test for two correlated ROC curves
data: roc_list[["M6: MICE + Continuous"]] and roc_list[["M3: Continuous Nodes"]]
Z = 1.3128, p-value = 0.1893
alternative hypothesis: true difference in AUC is not equal to 0
95 percent confidence interval:
-0.004128663 0.020877634
sample estimates:
AUC of roc1 AUC of roc2
0.8114815 0.8031070
cat("\n=== H3 Balancing: SMOTE vs unadjusted (AUC + sensitivity) ===\n")
=== H3 Balancing: SMOTE vs unadjusted (AUC + sensitivity) ===
# Key point: SMOTE does NOT improve AUC (DeLong), even where it raises sensitivity.
M5 <- "M5: ALL (MICE + Cont + SMOTE)" # full name, matches roc_list / leaderboard
print(roc.test(roc_list[["M4: SMOTE on Baseline"]], roc_list[["M1: Baseline"]], method="delong"))
DeLong's test for two correlated ROC curves
data: roc_list[["M4: SMOTE on Baseline"]] and roc_list[["M1: Baseline"]]
Z = -1.8832, p-value = 0.05968
alternative hypothesis: true difference in AUC is not equal to 0
95 percent confidence interval:
-0.068278205 0.001364625
sample estimates:
AUC of roc1 AUC of roc2
0.6840947 0.7175514
print(roc.test(roc_list[[M5]], roc_list[["M6: MICE + Continuous"]], method="delong"))
DeLong's test for two correlated ROC curves
data: roc_list[[M5]] and roc_list[["M6: MICE + Continuous"]]
Z = -1.07, p-value = 0.2846
alternative hypothesis: true difference in AUC is not equal to 0
95 percent confidence interval:
-0.07889048 0.02317031
sample estimates:
AUC of roc1 AUC of roc2
0.7836214 0.8114815
mcnemar_sens(M5, "M6: MICE + Continuous")
McNemar (sensitivity) M5: ALL (MICE + Cont + SMOTE) vs M6: MICE + Continuous:
McNemar's Chi-squared test with continuity correction
data: tab
McNemar's chi-squared = 0.57143, df = 1, p-value = 0.4497
cat("\n=== H4 Synergy: best continuous vs absolute baseline ===\n")
=== H4 Synergy: best continuous vs absolute baseline ===
print(roc.test(roc_list[["M6: MICE + Continuous"]], roc_list[["M1: Baseline"]], method="delong"))
DeLong's test for two correlated ROC curves
data: roc_list[["M6: MICE + Continuous"]] and roc_list[["M1: Baseline"]]
Z = 2.5075, p-value = 0.01216
alternative hypothesis: true difference in AUC is not equal to 0
95 percent confidence interval:
0.02051173 0.16734835
sample estimates:
AUC of roc1 AUC of roc2
0.8114815 0.7175514
#13b Hypothesis test and p-value re-run
library(pROC)
# Validation-tuned threshold for each model (read from the leaderboard).
thr <- function(m) master_leaderboard$Val_Threshold[master_leaderboard$Model == m]
# McNemar on the actual-stroke cohort: does model A catch cases B misses?
mcnemar_sens <- function(A, B){
y <- roc_list[[A]]$response
pa <- ifelse(roc_list[[A]]$predictor >= thr(A), 1, 0)
pb <- ifelse(roc_list[[B]]$predictor >= thr(B), 1, 0)
idx <- which(y == 1)
tab <- table(A = factor(pa[idx], 0:1), B = factor(pb[idx], 0:1))
p <- suppressWarnings(mcnemar.test(tab)$p.value)
cat(sprintf("McNemar sens %-30s vs %-24s : p = %.4f\n", A, B, p))
data.frame(test = "McNemar(sens)", A = A, B = B, stat = NA, p = round(p, 4))
}
# DeLong AUC comparison.
delong <- function(A, B){
t <- roc.test(roc_list[[A]], roc_list[[B]], method = "delong")
cat(sprintf("DeLong AUC %-30s vs %-24s : Z = %.3f, p = %.4f\n",
A, B, as.numeric(t$statistic), t$p.value))
data.frame(test = "DeLong(AUC)", A = A, B = B,
stat = round(as.numeric(t$statistic), 3), p = round(t$p.value, 4))
}
M5 <- "M5: ALL (MICE + Cont + SMOTE)"
res <- list()
cat("\n=== H1 topology, minority sensitivity (McNemar) ===\n")
=== H1 topology, minority sensitivity (McNemar) ===
res[[1]] <- mcnemar_sens("M3: Continuous Nodes", "M1: Baseline")
McNemar sens M3: Continuous Nodes vs M1: Baseline : p = 0.0098
res[[2]] <- mcnemar_sens("M6: MICE + Continuous", "M2: MICE + Discretize")
McNemar sens M6: MICE + Continuous vs M2: MICE + Discretize : p = 0.0990
cat("\n=== H3 SMOTE, minority sensitivity (McNemar) ===\n")
=== H3 SMOTE, minority sensitivity (McNemar) ===
res[[3]] <- mcnemar_sens("M4: SMOTE on Baseline", "M1: Baseline")
McNemar sens M4: SMOTE on Baseline vs M1: Baseline : p = 0.1824
res[[4]] <- mcnemar_sens("M8: MICE + SMOTE", "M2: MICE + Discretize")
McNemar sens M8: MICE + SMOTE vs M2: MICE + Discretize : p = 0.2278
res[[5]] <- mcnemar_sens(M5, "M6: MICE + Continuous")
McNemar sens M5: ALL (MICE + Cont + SMOTE) vs M6: MICE + Continuous : p = 0.4497
cat("\n=== H4 synergy vs absolute baseline (DeLong AUC) ===\n")
=== H4 synergy vs absolute baseline (DeLong AUC) ===
res[[6]] <- delong(M5, "M1: Baseline")
DeLong AUC M5: ALL (MICE + Cont + SMOTE) vs M1: Baseline : Z = 1.767, p = 0.0772
res[[7]] <- delong("M6: MICE + Continuous", "M1: Baseline")
DeLong AUC M6: MICE + Continuous vs M1: Baseline : Z = 2.508, p = 0.0122
pval_table <- do.call(rbind, res)
print(pval_table)
test A B stat p
1 McNemar(sens) M3: Continuous Nodes M1: Baseline NA 0.0098
2 McNemar(sens) M6: MICE + Continuous M2: MICE + Discretize NA 0.0990
3 McNemar(sens) M4: SMOTE on Baseline M1: Baseline NA 0.1824
4 McNemar(sens) M8: MICE + SMOTE M2: MICE + Discretize NA 0.2278
5 McNemar(sens) M5: ALL (MICE + Cont + SMOTE) M6: MICE + Continuous NA 0.4497
6 DeLong(AUC) M5: ALL (MICE + Cont + SMOTE) M1: Baseline 1.767 0.0772
7 DeLong(AUC) M6: MICE + Continuous M1: Baseline 2.508 0.0122
write.csv(pval_table, "hypothesis_pvalues.csv", row.names = FALSE)
cat("\nSaved hypothesis_pvalues.csv\n")
Saved hypothesis_pvalues.csv
14 Robustness — 30 repeated stratified splits
# A single split is fragile on 249 events. Repeat the WHOLE pipeline over many
# seeds; report mean +/- SD AUC, and a PAIRED test of continuous vs discrete on
# the per-seed AUC differences. This is far stronger than one DeLong p-value.
# NOTE: continuous arms use per-patient likelihood weighting; n is lowered to 500
# here for runtime. Raise N_REPEATS / n for the final camera-ready run.
N_REPEATS <- 30
eval_light <- function(tr, va, te, disc){
wl <- if (disc) wl_discrete else wl_hybrid; bl <- if (disc) bl_discrete else bl_hybrid
dag <- hc(tr, whitelist=wl, blacklist=bl)
fit <- if (disc) bn.fit(dag, tr, method="bayes", iss=10) else bn.fit(dag, tr)
gp <- function(d){
if (disc){ pp <- predict(fit, node="stroke", data=d, method="bayes-lw", prob=TRUE)
return(attr(pp,"prob")["1",]) }
ec <- setdiff(names(d),"stroke")
vapply(seq_len(nrow(d)), function(i){
p <- suppressWarnings(cpquery(fit, event=(stroke=="1"),
evidence=as.list(d[i,ec]), method="lw", n=500)); if (is.na(p)) 0 else p}, numeric(1))
}
as.numeric(auc(roc(te$stroke, gp(te), levels=c("0","1"), quiet=TRUE)))
}
rob <- data.frame()
for (s in 1:N_REPEATS) {
sp <- split_stroke(stroke_data, seed = 1000 + s)
sp$train$split<-NULL; # safety
m <- median(as.numeric(as.character(sp$train$bmi)), na.rm=TRUE)
# MICE (leakage-safe) for this split
cb <- bind_rows(mutate(sp$train,split="train"), mutate(sp$val,split="val"),
mutate(sp$test,split="test")) %>% select(-any_of("id"))
mm <- suppressWarnings(mice(cb, m=1, ignore=cb$split!="train", maxit=5,
seed=1000+s, printFlag=FALSE))
dn <- complete(mm,1)
tr2<-dn%>%filter(split=="train")%>%select(-split)
te2<-dn%>%filter(split=="test") %>%select(-split)
safe <- function(expr) tryCatch(expr, error=function(e){ cat(" (skipped:",e$message,")\n"); NA_real_ })
rob <- rbind(rob, data.frame(seed=s,
M1=safe(eval_light(impute_baseline(sp$train,m), NULL, impute_baseline(sp$test,m), TRUE)),
M2=safe(eval_light(impute_baseline(tr2,m), NULL, impute_baseline(te2,m), TRUE)),
M3=safe(eval_light(prep_continuous(sp$train,m), NULL, prep_continuous(sp$test,m), FALSE)),
M6=safe(eval_light(prep_continuous(tr2,m), NULL, prep_continuous(te2,m), FALSE))))
cat(sprintf("robust seed %d/%d done\n", s, N_REPEATS))
}
robust seed 1/30 done
robust seed 2/30 done
robust seed 3/30 done
robust seed 4/30 done
robust seed 5/30 done
robust seed 6/30 done
robust seed 7/30 done
robust seed 8/30 done
robust seed 9/30 done
robust seed 10/30 done
robust seed 11/30 done
robust seed 12/30 done
(skipped: 'Residence_type' has different number of levels in the node and in the data. )
(skipped: 'Residence_type' has different number of levels in the node and in the data. )
robust seed 13/30 done
robust seed 14/30 done
robust seed 15/30 done
robust seed 16/30 done
robust seed 17/30 done
robust seed 18/30 done
robust seed 19/30 done
robust seed 20/30 done
robust seed 21/30 done
robust seed 22/30 done
robust seed 23/30 done
robust seed 24/30 done
robust seed 25/30 done
robust seed 26/30 done
robust seed 27/30 done
robust seed 28/30 done
robust seed 29/30 done
robust seed 30/30 done
rob_summary <- data.frame(
Arm = c("M1: Baseline","M2: MICE+Disc","M3: Continuous","M6: MICE+Continuous"),
AUC_mean = round(c(mean(rob$M1,na.rm=TRUE), mean(rob$M2,na.rm=TRUE), mean(rob$M3,na.rm=TRUE), mean(rob$M6,na.rm=TRUE)),4),
AUC_sd = round(c(sd(rob$M1,na.rm=TRUE), sd(rob$M2,na.rm=TRUE), sd(rob$M3,na.rm=TRUE), sd(rob$M6,na.rm=TRUE)),4))
print(rob_summary); write.csv(rob_summary, "robustness_summary.csv", row.names=FALSE)
Arm AUC_mean AUC_sd
1 M1: Baseline 0.7696 0.0392
2 M2: MICE+Disc 0.7693 0.0402
3 M3: Continuous 0.8209 0.0284
4 M6: MICE+Continuous 0.8202 0.0283
cat("\nPaired tests (continuous vs discrete) across", N_REPEATS, "seeds:\n")
Paired tests (continuous vs discrete) across 30 seeds:
cat("H1 primary M3 vs M1:\n"); print(t.test(rob$M3, rob$M1, paired=TRUE)); print(wilcox.test(rob$M3, rob$M1, paired=TRUE))
H1 primary M3 vs M1:
Paired t-test
data: rob$M3 and rob$M1
t = 8.1395, df = 28, p-value = 7.333e-09
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
0.03899015 0.06521447
sample estimates:
mean difference
0.05210231
Wilcoxon signed rank exact test
data: rob$M3 and rob$M1
V = 429, p-value = 5.215e-08
alternative hypothesis: true location shift is not equal to 0
cat("H1 support M6 vs M2:\n"); print(t.test(rob$M6, rob$M2, paired=TRUE)); print(wilcox.test(rob$M6, rob$M2, paired=TRUE))
H1 support M6 vs M2:
Paired t-test
data: rob$M6 and rob$M2
t = 8.1929, df = 28, p-value = 6.434e-09
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
0.03875949 0.06460222
sample estimates:
mean difference
0.05168086
Wilcoxon signed rank exact test
data: rob$M6 and rob$M2
V = 426, p-value = 1.229e-07
alternative hypothesis: true location shift is not equal to 0
write.csv(rob, "robustness_raw_auc.csv", row.names=FALSE)
15 Clinical inference & what-if scenarios
fitted_net <- fit_list[["M6: MICE + Continuous"]]
set.seed(2026); n_sim <- 1e6 # logic sampling; seed fixes the reported values
# Evidence must be written INLINE: cpquery uses non-standard evaluation and will
# not accept a quoted expression passed through a variable.
pA <- cpquery(fitted_net, event=(stroke=="1"),
evidence=(age<=50 & hypertension=="0" & heart_disease=="0"), n=n_sim)
pB <- cpquery(fitted_net, event=(stroke=="1"),
evidence=(age>=65 & hypertension=="1" & heart_disease=="1" & avg_glucose_level>=150), n=n_sim)
pC <- cpquery(fitted_net, event=(stroke=="1"),
evidence=(age>=65 & hypertension=="1" & heart_disease=="1" & avg_glucose_level<=100), n=n_sim)
cat(sprintf("Persona A (low risk): %.2f%%\n", 100*pA))
Persona A (low risk): 0.74%
cat(sprintf("Persona B (high risk): %.2f%%\n", 100*pB))
Persona B (high risk): 43.18%
cat(sprintf("Persona C (B + glucose managed): %.2f%%\n", 100*pC))
Persona C (B + glucose managed): 31.55%
cat(sprintf("Absolute risk reduction: %.2f%% -> %.2f%%\n", 100*pB, 100*pC))
Absolute risk reduction: 43.18% -> 31.55%
16 Session info (reproducibility record)
writeLines(capture.output(sessionInfo()), "sessionInfo.txt")
sessionInfo()
R version 4.5.2 (2025-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26200)
Matrix products: default
LAPACK version 3.12.1
locale:
[1] LC_COLLATE=English_Canada.utf8 LC_CTYPE=English_Canada.utf8 LC_MONETARY=English_Canada.utf8
[4] LC_NUMERIC=C LC_TIME=English_Canada.utf8
time zone: America/Toronto
tzcode source: internal
attached base packages:
[1] grid stats graphics grDevices utils datasets methods base
other attached packages:
[1] xgboost_3.2.1.1 vcd_1.4-13 themis_1.0.3 recipes_1.3.1 mice_3.19.0 pROC_1.19.0.1 caret_7.0-1
[8] lattice_0.22-7 bnlearn_5.1 lubridate_1.9.4 forcats_1.0.1 stringr_1.6.0 dplyr_1.1.4 purrr_1.2.0
[15] readr_2.1.6 tidyr_1.3.1 tibble_3.3.0 ggplot2_4.0.1 tidyverse_2.0.0
loaded via a namespace (and not attached):
[1] Rdpack_2.6.4 rlang_1.1.6 magrittr_2.0.4 otel_0.2.0 e1071_1.7-17
[6] compiler_4.5.2 systemfonts_1.3.1 vctrs_0.6.5 reshape2_1.4.5 pkgconfig_2.0.3
[11] shape_1.4.6.1 fastmap_1.2.0 backports_1.5.0 labeling_0.4.3 utf8_1.2.6
[16] rmarkdown_2.30 prodlim_2025.04.28 effsize_0.8.1 tzdb_0.5.0 pracma_2.4.6
[21] graph_1.88.1 nloptr_2.2.1 ragg_1.5.0 xfun_0.57 glmnet_4.1-10
[26] jomo_2.7-6 cachem_1.1.0 jsonlite_2.0.0 pan_1.9 broom_1.0.11
[31] parallel_4.5.2 R6_2.6.1 bslib_0.9.0 stringi_1.8.7 RColorBrewer_1.1-3
[36] parallelly_1.46.1 car_3.1-5 boot_1.3-32 rpart_4.1.24 lmtest_0.9-40
[41] jquerylib_0.1.4 Rcpp_1.1.0 iterators_1.0.14 knitr_1.51 future.apply_1.20.1
[46] zoo_1.8-15 Matrix_1.7-4 splines_4.5.2 nnet_7.3-20 timechange_0.3.0
[51] tidyselect_1.2.1 rstudioapi_0.17.1 abind_1.4-8 yaml_2.3.12 timeDate_4051.111
[56] codetools_0.2-20 listenv_0.10.0 plyr_1.8.9 withr_3.0.2 S7_0.2.1
[61] evaluate_1.0.5 future_1.69.0 survival_3.8-3 proxy_0.4-29 pillar_1.11.1
[66] ggpubr_0.6.3 carData_3.0-6 rsconnect_1.7.0 foreach_1.5.2 stats4_4.5.2
[71] reformulas_0.4.3.1 generics_0.1.4 hms_1.1.4 scales_1.4.0 minqa_1.2.8
[76] globals_0.18.0 class_7.3-23 glue_1.8.0 ROSE_0.0-4 tools_4.5.2
[81] data.table_1.17.8 lme4_1.1-38 ModelMetrics_1.2.2.2 gower_1.0.2 ggsignif_0.6.4
[86] rbibutils_2.4 colorspace_2.1-2 ipred_0.9-15 nlme_3.1-168 Formula_1.2-5
[91] cli_3.6.5 textshaping_1.0.4 lava_1.8.2 Rgraphviz_2.54.0 gtable_0.3.6
[96] rstatix_0.7.3 sass_0.4.10 digest_0.6.39 BiocGenerics_0.56.0 farver_2.1.2
[101] htmltools_0.5.9 lifecycle_1.0.5 hardhat_1.4.2 mitml_0.4-5 MASS_7.3-65
LS0tDQp0aXRsZTogIkh5YnJpZCBCYXllc2lhbiBOZXR3b3JrIGZvciBJbnRlcnByZXRhYmxlIFN0cm9rZSBQcmVkaWN0aW9uIOKAlCBGaW5hbCBBbmFseXNpcyBQaXBlbGluZSINCmF1dGhvcjogIlByYW5pbCBHQywgUmF2aW5kZXItSmVldCBTaW5naCwgUmF0dmluZGVyIEdyZXdhbCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDINCi0tLQ0KDQo8IS0tDQo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQogUkVBREVSJ1MgR1VJREUgKGZvciByZXZpZXdlcnMpDQogVGhlIG5vdGVib29rIGlzIG9yZ2FuaXNlZCBpbnRvIHNlbGYtY29udGFpbmVkLCBudW1iZXJlZCBTRUdNRU5UUy4gUnVuIHRoZW0NCiB0b3AgdG8gYm90dG9tOyBlYWNoIHNlZ21lbnQgZGVwZW5kcyBvbmx5IG9uIHRoZSBvYmplY3RzIGNyZWF0ZWQgYWJvdmUgaXQuDQoNCiAgIDAwICBTZXR1cCAmIHJlcHJvZHVjaWJpbGl0eQ0KICAgMDEgIExvYWQgJiBjbGVhbiBkYXRhDQogICAwMiAgRXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyAoRURBKQ0KICAgMDMgIFN0cmF0aWZpZWQgNjAvMjAvMjAgc3BsaXQNCiAgIDA0ICBQcmVwcm9jZXNzaW5nIGhlbHBlcnMgKGltcHV0ZSAvIGRpc2NyZXRpc2UgLyBjb250aW51b3VzIC8gU01PVEVOQykNCiAgIDA1ICBCdWlsZCB0aGUgOCBhYmxhdGlvbiBhcm1zIChNMeKAk004KQ0KICAgMDYgIEZlYXR1cmUgYXNzb2NpYXRpb24gc2NyZWVuIChkcml2ZXMgd2hpdGVsaXN0L2JsYWNrbGlzdCkNCiAgIDA3ICBTdHJ1Y3R1cmFsIGNvbnN0cmFpbnRzICh3aGl0ZWxpc3QgLyBibGFja2xpc3QpDQogICAwOCAgQ29yZSBldmFsdWF0aW9uIGZ1bmN0aW9uDQogICAwOSAgTUFJTiBSRVNVTFQ6IHRyYWluICYgcmFuayB0aGUgOCBhcm1zIChyYW5rZWQgYnkgQVVDLCB0aGVuIFlvdWRlbidzIEopDQogICAxMCAgUk9DIGZpZ3VyZQ0KICAgMTEgIENhbGlicmF0aW9uICYgQnJpZXIgc2NvcmUgIChORVcg4oCUIHN1cHBvcnRzIHRoZSBTTU9URSBjbGFpbSkNCiAgIDEyICBPcGFxdWUtbW9kZWwgYmVuY2htYXJrOiBsb2dpc3RpYyByZWdyZXNzaW9uICYgWEdCb29zdCAoTkVXKQ0KICAgMTMgIEh5cG90aGVzaXMgdGVzdGluZzogRGVMb25nIChBVUMpICsgTWNOZW1hciAoc2Vuc2l0aXZpdHkpDQogICAxNCAgUm9idXN0bmVzczogMzAgcmVwZWF0ZWQgc3RyYXRpZmllZCBzcGxpdHMgKyBwYWlyZWQgdGVzdHMgKE5FVykNCiAgIDE1ICBDbGluaWNhbCBpbmZlcmVuY2UgJiB3aGF0LWlmIHNjZW5hcmlvcw0KICAgMTYgIFNlc3Npb24gaW5mbyAoTkVXIOKAlCByZXByb2R1Y2liaWxpdHkgcmVjb3JkKQ0KDQogS0VZIERFU0lHTiBERUNJU0lPTlMNCiAqIE5vIGRhdGEgbGVha2FnZTogbWVkaWFuICYgTUlDRSBhcmUgbGVhcm5lZCBvbiBUUkFJTiBvbmx5OyB0aGUgZGVjaXNpb24NCiAgIHRocmVzaG9sZCBpcyB0dW5lZCBvbiBWQUxJREFUSU9OIGFuZCBhcHBsaWVkIHRvIGEgYmxpbmQgVEVTVCBzZXQuDQogKiBSYW5raW5nIGlzIGJ5IEFVQyAodGhyZXNob2xkLWluZGVwZW5kZW50LCBzdGFibGUpLiBZb3VkZW4ncyBKIGlzIHJlcG9ydGVkDQogICBhcyBhIHNlY29uZGFyeSBtZXRyaWMgYmVjYXVzZSBpdCBkZXBlbmRzIG9uIGEgc2luZ2xlIHR1bmVkIHRocmVzaG9sZC4NCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCi0tPg0KDQojIDAwICBTZXR1cCAmIHJlcHJvZHVjaWJpbGl0eQ0KYGBge3Igc2V0dXB9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoYm5sZWFybikNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHBST0MpDQpsaWJyYXJ5KG1pY2UpDQpsaWJyYXJ5KHJlY2lwZXMpDQpsaWJyYXJ5KHRoZW1pcykNCg0KIyBPbmUgZ2xvYmFsIHNlZWQgZ292ZXJucyB0aGUgd2hvbGUgc2luZ2xlLXNwbGl0IHBpcGVsaW5lLg0KR0xPQkFMX1NFRUQgPC0gNDINCnNldC5zZWVkKEdMT0JBTF9TRUVEKQ0KYGBgDQoNCiMgMDEgIExvYWQgJiBjbGVhbiBkYXRhDQpgYGB7ciBsb2FkLWNsZWFufQ0Kc3Ryb2tlX2RhdGEgPC0gcmVhZC5jc3YoImhlYWx0aGNhcmUtZGF0YXNldC1zdHJva2UtZGF0YS5jc3YiKQ0KDQpzdHJva2VfZGF0YSA8LSBzdHJva2VfZGF0YSAlPiUNCiAgZmlsdGVyKGdlbmRlciAhPSAiT3RoZXIiKSAlPiUgICAgICAjIHNpbmdsZSBhbWJpZ3VvdXMgcmVjb3JkDQogIGRyb3BsZXZlbHMoKSAlPiUNCiAgbXV0YXRlKA0KICAgIGJtaSAgICAgICAgICAgID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYm1pKSksICAgIyAiTi9BIiBzdHJpbmdzIC0+IE5BDQogICAgc3Ryb2tlICAgICAgICAgPSBhcy5mYWN0b3Ioc3Ryb2tlKSwNCiAgICBoeXBlcnRlbnNpb24gICA9IGFzLmZhY3RvcihoeXBlcnRlbnNpb24pLA0KICAgIGhlYXJ0X2Rpc2Vhc2UgID0gYXMuZmFjdG9yKGhlYXJ0X2Rpc2Vhc2UpLA0KICAgIGdlbmRlciAgICAgICAgID0gYXMuZmFjdG9yKGdlbmRlciksDQogICAgZXZlcl9tYXJyaWVkICAgPSBhcy5mYWN0b3IoZXZlcl9tYXJyaWVkKSwNCiAgICB3b3JrX3R5cGUgICAgICA9IGFzLmZhY3Rvcih3b3JrX3R5cGUpLA0KICAgIFJlc2lkZW5jZV90eXBlID0gYXMuZmFjdG9yKFJlc2lkZW5jZV90eXBlKSwNCiAgICBzbW9raW5nX3N0YXR1cyA9IGFzLmZhY3RvcihzbW9raW5nX3N0YXR1cykNCiAgKQ0KDQpjYXQoc3ByaW50ZigiTiA9ICVkIHBhdGllbnRzIHwgc3Ryb2tlIHByZXZhbGVuY2UgPSAlLjFmJSVcbiIsDQogICAgICAgICAgICBucm93KHN0cm9rZV9kYXRhKSwgMTAwICogbWVhbihzdHJva2VfZGF0YSRzdHJva2UgPT0gIjEiKSkpDQpgYGANCg0KIyAwMiAgRXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyAob3B0aW9uYWwsIGZvciBmaWd1cmVzKQ0KYGBge3IgZWRhLCBldmFsPUZBTFNFfQ0KIyBTZXQgZXZhbD1UUlVFIHRvIHJlZ2VuZXJhdGUgdGhlIEVEQSBmaWd1cmVzIHVzZWQgaW4gdGhlIG1hbnVzY3JpcHQuDQojIChCYXIgY2hhcnRzIGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIGJveCBwbG90cyBmb3IgY29udGludW91cyB2YXJpYWJsZXMuKQ0KIyAtLS0ga2VwdCBmcm9tIHRoZSBvcmlnaW5hbCBub3RlYm9vazsgb21pdHRlZCBoZXJlIGZvciBicmV2aXR5IG9mIHRoZSBydW4gLS0tDQpgYGANCg0KIyAwMyAgU3RyYXRpZmllZCA2MC8yMC8yMCBzcGxpdA0KYGBge3Igc3BsaXR9DQojIFN0cmF0aWZpZWQgc3BsaXQgdGhhdCBwcmVzZXJ2ZXMgdGhlIChyYXJlKSBzdHJva2UgcHJldmFsZW5jZSBpbiBlYWNoIGZvbGQuDQojIEEgYHNlZWRgIGFyZ3VtZW50IGlzIGV4cG9zZWQgc28gdGhlIFNBTUUgZnVuY3Rpb24gY2FuIGRyaXZlIHRoZSByZXBlYXRlZC1zcGxpdA0KIyByb2J1c3RuZXNzIGFuYWx5c2lzIGluIFNlZ21lbnQgMTQuDQpzcGxpdF9zdHJva2UgPC0gZnVuY3Rpb24oZGF0YSwgc2VlZCA9IEdMT0JBTF9TRUVELCBwX3RyYWluID0gMC42LCBwX3ZhbCA9IDAuMikgew0KICBzZXQuc2VlZChzZWVkKQ0KICBzMCA8LSBkYXRhICU+JSBmaWx0ZXIoc3Ryb2tlID09ICIwIik7IHMxIDwtIGRhdGEgJT4lIGZpbHRlcihzdHJva2UgPT0gIjEiKQ0KICBuMCA8LSBucm93KHMwKTsgbjEgPC0gbnJvdyhzMSkNCg0KICB0cjAgPC0gc2FtcGxlKG4wLCByb3VuZChuMCAqIHBfdHJhaW4pKTsgdHIxIDwtIHNhbXBsZShuMSwgcm91bmQobjEgKiBwX3RyYWluKSkNCiAgdmEwIDwtIHNhbXBsZShzZXRkaWZmKHNlcV9sZW4objApLCB0cjApLCByb3VuZChuMCAqIHBfdmFsKSkNCiAgdmExIDwtIHNhbXBsZShzZXRkaWZmKHNlcV9sZW4objEpLCB0cjEpLCByb3VuZChuMSAqIHBfdmFsKSkNCg0KICBsaXN0KA0KICAgIHRyYWluID0gYmluZF9yb3dzKHMwW3RyMCwgXSwgczFbdHIxLCBdKSwNCiAgICB2YWwgICA9IGJpbmRfcm93cyhzMFt2YTAsIF0sIHMxW3ZhMSwgXSksDQogICAgdGVzdCAgPSBiaW5kX3Jvd3MoczBbc2V0ZGlmZihzZXFfbGVuKG4wKSwgYyh0cjAsIHZhMCkpLCBdLA0KICAgICAgICAgICAgICAgICAgICAgIHMxW3NldGRpZmYoc2VxX2xlbihuMSksIGModHIxLCB2YTEpKSwgXSkNCiAgKQ0KfQ0KDQpzcGxpdHMgICA8LSBzcGxpdF9zdHJva2Uoc3Ryb2tlX2RhdGEsIHNlZWQgPSBHTE9CQUxfU0VFRCkNCnRyYWluc2V0IDwtIHNwbGl0cyR0cmFpbjsgdmFsc2V0IDwtIHNwbGl0cyR2YWw7IHRlc3RzZXQgPC0gc3BsaXRzJHRlc3QNCg0KZm9yIChubSBpbiBjKCJ0cmFpbnNldCIsICJ2YWxzZXQiLCAidGVzdHNldCIpKSB7DQogIGQgPC0gZ2V0KG5tKTsgcCA8LSBwcm9wLnRhYmxlKHRhYmxlKGQkc3Ryb2tlKSkNCiAgY2F0KHNwcmludGYoIiUtOXMgbj0lNGQgfCBzdHJva2U9JS4zZlxuIiwgbm0sIG5yb3coZCksIHBbIjEiXSkpDQp9DQpgYGANCg0KIyAwNCAgUHJlcHJvY2Vzc2luZyBoZWxwZXJzDQpgYGB7ciBoZWxwZXJzfQ0KIyAtLS0gKGEpIERpc2NyZXRpc2luZyBpbXB1dGVyOiBtZWRpYW4gQk1JIGZyb20gVFJBSU4gb25seSwgdGhlbiBXSE8vQ0RDL0FEQSBiaW5zDQppbXB1dGVfYmFzZWxpbmUgPC0gZnVuY3Rpb24oZGF0YSwgdHJhaW5fbWVkaWFuX2JtaSkgew0KICBkYXRhICU+JQ0KICAgIG11dGF0ZShibWkgPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihibWkpKSwNCiAgICAgICAgICAgYm1pID0gaWZlbHNlKGlzLm5hKGJtaSksIHRyYWluX21lZGlhbl9ibWksIGJtaSkpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIGFnZSA9IGN1dChhZ2UsIGJyZWFrcyA9IGMoc2VxKDAsIDg1LCBieSA9IDUpLCBJbmYpLCByaWdodCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjAtNCIsIjUtOSIsIjEwLTE0IiwiMTUtMTkiLCIyMC0yNCIsIjI1LTI5IiwiMzAtMzQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIjM1LTM5IiwiNDAtNDQiLCI0NS00OSIsIjUwLTU0IiwiNTUtNTkiLCI2MC02NCIsIjY1LTY5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICI3MC03NCIsIjc1LTc5IiwiODAtODQiLCI4NSsiKSksDQogICAgICBibWkgPSBjdXQoYm1pLCBicmVha3MgPSBjKDAsIDE4LjUsIDI1LCAzMCwgSW5mKSwgcmlnaHQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJVbmRlcndlaWdodCIsIk5vcm1hbCIsIk92ZXJ3ZWlnaHQiLCJPYmVzZSIpKSwNCiAgICAgIGF2Z19nbHVjb3NlX2xldmVsID0gY3V0KGF2Z19nbHVjb3NlX2xldmVsLCBicmVha3MgPSBjKDAsIDEwMCwgMTI2LCBJbmYpLA0KICAgICAgICAgICAgICAgIHJpZ2h0ID0gRkFMU0UsIGxhYmVscyA9IGMoIk5vcm1hbCIsIlByZWRpYWJldGVzIiwiRGlhYmV0ZXMiKSkNCiAgICApICU+JQ0KICAgIG11dGF0ZShhY3Jvc3MoYyhnZW5kZXIsaHlwZXJ0ZW5zaW9uLGhlYXJ0X2Rpc2Vhc2UsZXZlcl9tYXJyaWVkLA0KICAgICAgICAgICAgICAgICAgICB3b3JrX3R5cGUsUmVzaWRlbmNlX3R5cGUsc21va2luZ19zdGF0dXMsc3Ryb2tlKSwgYXMuZmFjdG9yKSkgJT4lDQogICAgZHJvcGxldmVscygpICU+JSBzZWxlY3QoLWFueV9vZigiaWQiKSkNCn0NCg0KIyAtLS0gKGIpIENvbnRpbnVvdXMvaHlicmlkIHByZXA6IGtlZXAgYWdlL2JtaS9nbHVjb3NlIG51bWVyaWMNCnByZXBfY29udGludW91cyA8LSBmdW5jdGlvbihkZiwgdHJhaW5fbWVkaWFuX2JtaSkgew0KICBkZiAlPiUNCiAgICBtdXRhdGUoYm1pID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYm1pKSksDQogICAgICAgICAgIGJtaSA9IGlmZWxzZShpcy5uYShibWkpLCB0cmFpbl9tZWRpYW5fYm1pLCBibWkpLA0KICAgICAgICAgICBhdmdfZ2x1Y29zZV9sZXZlbCA9IGFzLm51bWVyaWMoYXZnX2dsdWNvc2VfbGV2ZWwpLA0KICAgICAgICAgICBhZ2UgPSBhcy5udW1lcmljKGFnZSkpICU+JQ0KICAgIG11dGF0ZShhY3Jvc3MoYyhnZW5kZXIsaHlwZXJ0ZW5zaW9uLGhlYXJ0X2Rpc2Vhc2UsZXZlcl9tYXJyaWVkLA0KICAgICAgICAgICAgICAgICAgICB3b3JrX3R5cGUsUmVzaWRlbmNlX3R5cGUsc21va2luZ19zdGF0dXMsc3Ryb2tlKSwgYXMuZmFjdG9yKSkgJT4lDQogICAgc2VsZWN0KC1hbnlfb2YoImlkIikpDQp9DQoNCiMgLS0tIChjKSBTTU9URU5DIHN5bnRoZXRpYyBvdmVyc2FtcGxpbmcgZm9yIG1peGVkIChjYXRlZ29yaWNhbCtjb250aW51b3VzKSBkYXRhDQphcHBseV9zbW90ZW5jIDwtIGZ1bmN0aW9uKGRmLCB0YXJnZXRfdmFyID0gInN0cm9rZSIpIHsNCiAgZGYgPC0gZHJvcGxldmVscyhhcy5kYXRhLmZyYW1lKGRmKSkNCiAgcmVjIDwtIHJlY2lwZShmb3JtdWxhKHBhc3RlKHRhcmdldF92YXIsICJ+IC4iKSksIGRhdGEgPSBkZikgJT4lDQogICAgc3RlcF9zbW90ZW5jKGFsbF9vdXRjb21lcygpLCBvdmVyX3JhdGlvID0gMSwgc2VlZCA9IEdMT0JBTF9TRUVEKSAlPiUgcHJlcCgpDQogIG91dCA8LSBqdWljZShyZWMpICU+JQ0KICAgIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIGFzLm51bWVyaWMpLA0KICAgICAgICAgICBhY3Jvc3Mod2hlcmUoaXMuaW50ZWdlciksIGFzLm51bWVyaWMpLA0KICAgICAgICAgICBhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgYXMuZmFjdG9yKSkNCiAgZHJvcGxldmVscyhhcy5kYXRhLmZyYW1lKG91dCkpDQp9DQpgYGANCg0KIyAwNSAgQnVpbGQgdGhlIDggYWJsYXRpb24gYXJtcyAoTTHigJNNOCkNCmBgYHtyIHZhcmlhbnRzfQ0KIyBMZWFrYWdlLXNhZmUgTUlDRTogY29tYmluZSB0aGUgZm9sZHMgYnV0IGBpZ25vcmVgIGV2ZXJ5dGhpbmcgZXhjZXB0IFRSQUlOIHJvd3MsDQojIHNvIGltcHV0YXRpb24gcnVsZXMgYXJlIGxlYXJuZWQgZnJvbSB0cmFpbmluZyBkYXRhIG9ubHkuDQp0cmFpbnNldCRzcGxpdCA8LSAidHJhaW4iOyB2YWxzZXQkc3BsaXQgPC0gInZhbCI7IHRlc3RzZXQkc3BsaXQgPC0gInRlc3QiDQpjb21iaW5lZCA8LSBiaW5kX3Jvd3ModHJhaW5zZXQsIHZhbHNldCwgdGVzdHNldCkgJT4lIHNlbGVjdCgtYW55X29mKCJpZCIpKQ0KbWljZV9zYWZlIDwtIHN1cHByZXNzV2FybmluZ3MoDQogIG1pY2UoY29tYmluZWQsIG0gPSAxLCBpZ25vcmUgPSBjb21iaW5lZCRzcGxpdCAhPSAidHJhaW4iLA0KICAgICAgIG1heGl0ID0gNSwgc2VlZCA9IEdMT0JBTF9TRUVELCBwcmludEZsYWcgPSBGQUxTRSkpDQpkb25lIDwtIGNvbXBsZXRlKG1pY2Vfc2FmZSwgMSkNCnRyYWluX20yX3JhdyA8LSBkb25lICU+JSBmaWx0ZXIoc3BsaXQ9PSJ0cmFpbiIpICU+JSBzZWxlY3QoLXNwbGl0KQ0KdmFsX20yX3JhdyAgIDwtIGRvbmUgJT4lIGZpbHRlcihzcGxpdD09InZhbCIpICAgJT4lIHNlbGVjdCgtc3BsaXQpDQp0ZXN0X20yX3JhdyAgPC0gZG9uZSAlPiUgZmlsdGVyKHNwbGl0PT0idGVzdCIpICAlPiUgc2VsZWN0KC1zcGxpdCkNCnRyYWluc2V0IDwtIHRyYWluc2V0ICU+JSBzZWxlY3QoLXNwbGl0KQ0KdmFsc2V0ICAgPC0gdmFsc2V0ICAgJT4lIHNlbGVjdCgtc3BsaXQpDQp0ZXN0c2V0ICA8LSB0ZXN0c2V0ICAlPiUgc2VsZWN0KC1zcGxpdCkNCg0KbWVkIDwtIG1lZGlhbihhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih0cmFpbnNldCRibWkpKSwgbmEucm0gPSBUUlVFKSAgIyBUUkFJTiBtZWRpYW4NCg0KIyBGYWN0b3JpYWwgMngyeDI6IHtkaXNjcmV0ZXxjb250aW51b3VzfSB4IHttZWRpYW58TUlDRX0geCB7bm9uZXxTTU9URX0NCnRyYWluX20xIDwtIGltcHV0ZV9iYXNlbGluZSh0cmFpbnNldCwgbWVkKTsgdmFsX20xIDwtIGltcHV0ZV9iYXNlbGluZSh2YWxzZXQsIG1lZCk7IHRlc3RfbTEgPC0gaW1wdXRlX2Jhc2VsaW5lKHRlc3RzZXQsIG1lZCkgICAjIGRpc2NyZXRlL21lZGlhbi9ub25lDQp0cmFpbl9tMiA8LSBpbXB1dGVfYmFzZWxpbmUodHJhaW5fbTJfcmF3LCBtZWQpOyB2YWxfbTIgPC0gaW1wdXRlX2Jhc2VsaW5lKHZhbF9tMl9yYXcsIG1lZCk7IHRlc3RfbTIgPC0gaW1wdXRlX2Jhc2VsaW5lKHRlc3RfbTJfcmF3LCBtZWQpICMgZGlzY3JldGUvTUlDRS9ub25lDQp0cmFpbl9tMyA8LSBwcmVwX2NvbnRpbnVvdXModHJhaW5zZXQsIG1lZCk7IHZhbF9tMyA8LSBwcmVwX2NvbnRpbnVvdXModmFsc2V0LCBtZWQpOyB0ZXN0X20zIDwtIHByZXBfY29udGludW91cyh0ZXN0c2V0LCBtZWQpICAgIyBjb250aW51b3VzL21lZGlhbi9ub25lDQoNCnRyYWluX200IDwtIGFwcGx5X3Ntb3RlbmModHJhaW5fbTEpOyB2YWxfbTQgPC0gdmFsX20xOyB0ZXN0X200IDwtIHRlc3RfbTEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZGlzY3JldGUvbWVkaWFuL1NNT1RFDQp0cmFpbl9tNV9iYXNlIDwtIHByZXBfY29udGludW91cyh0cmFpbl9tMl9yYXcsIG1lZCkNCnRyYWluX201IDwtIGFwcGx5X3Ntb3RlbmModHJhaW5fbTVfYmFzZSk7IHZhbF9tNSA8LSBwcmVwX2NvbnRpbnVvdXModmFsX20yX3JhdywgbWVkKTsgdGVzdF9tNSA8LSBwcmVwX2NvbnRpbnVvdXModGVzdF9tMl9yYXcsIG1lZCkgIyBjb250aW51b3VzL01JQ0UvU01PVEUNCnRyYWluX202IDwtIHRyYWluX201X2Jhc2U7IHZhbF9tNiA8LSB2YWxfbTU7IHRlc3RfbTYgPC0gdGVzdF9tNSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgY29udGludW91cy9NSUNFL25vbmUNCnRyYWluX203IDwtIGFwcGx5X3Ntb3RlbmModHJhaW5fbTMpOyB2YWxfbTcgPC0gdmFsX20zOyB0ZXN0X203IDwtIHRlc3RfbTMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgY29udGludW91cy9tZWRpYW4vU01PVEUNCnRyYWluX204IDwtIGFwcGx5X3Ntb3RlbmModHJhaW5fbTIpOyB2YWxfbTggPC0gdmFsX20xOyB0ZXN0X204IDwtIHRlc3RfbTEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZGlzY3JldGUvTUlDRS9TTU9URQ0KY2F0KCJBcm1zIE0xLU04IGJ1aWx0LlxuIikNCmBgYA0KDQojIDA2ICBGZWF0dXJlIGFzc29jaWF0aW9uIHNjcmVlbg0KYGBge3IgYXNzb2N9DQojIENyYW1lcidzIFYgKGNhdGVnb3JpY2FsKSwgcG9pbnQtYmlzZXJpYWwgKGNvbnRpbnVvdXMpIHZzIHN0cm9rZTsgdXNlZCB0bw0KIyBqdXN0aWZ5IHdoaWNoIGVkZ2VzIGFyZSB3aGl0ZWxpc3RlZC9ibGFja2xpc3RlZCBpbiBTZWdtZW50IDA3Lg0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHtsaWJyYXJ5KHZjZCl9KQ0KY2F0X3ZhcnMgPC0gYygiZ2VuZGVyIiwiaHlwZXJ0ZW5zaW9uIiwiaGVhcnRfZGlzZWFzZSIsImV2ZXJfbWFycmllZCIsDQogICAgICAgICAgICAgICJ3b3JrX3R5cGUiLCJSZXNpZGVuY2VfdHlwZSIsInNtb2tpbmdfc3RhdHVzIikNCm51bV92YXJzIDwtIGMoImFnZSIsImF2Z19nbHVjb3NlX2xldmVsIiwiYm1pIikNCmNhdF90YmwgPC0gbWFwX2RmcihjYXRfdmFycywgZnVuY3Rpb24odil7DQogIHRhYiA8LSB0YWJsZShzdHJva2VfZGF0YVtbdl1dLCBzdHJva2VfZGF0YSRzdHJva2UpDQogIGRhdGEuZnJhbWUoVmFyaWFibGU9diwgQXNzb2M9cm91bmQoYXNzb2NzdGF0cyh0YWIpJGNyYW1lciw0KSwNCiAgICAgICAgICAgICBQPWNoaXNxLnRlc3QodGFiKSRwLnZhbHVlKX0pDQpudW1fdGJsIDwtIG1hcF9kZnIobnVtX3ZhcnMsIGZ1bmN0aW9uKHYpew0KICBkYXRhLmZyYW1lKFZhcmlhYmxlPXYsDQogICAgICAgICAgICAgQXNzb2M9cm91bmQoY29yKHN0cm9rZV9kYXRhW1t2XV0sIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHN0cm9rZV9kYXRhJHN0cm9rZSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2U9ImNvbXBsZXRlLm9icyIpLDQpLA0KICAgICAgICAgICAgIFA9dC50ZXN0KHN0cm9rZV9kYXRhW1t2XV1+c3Ryb2tlX2RhdGEkc3Ryb2tlKSRwLnZhbHVlKX0pDQpwcmludChiaW5kX3Jvd3MoY2F0X3RibCwgbnVtX3RibCkgJT4lIGFycmFuZ2UoZGVzYyhhYnMoQXNzb2MpKSkpDQpgYGANCg0KIyAwNyAgU3RydWN0dXJhbCBjb25zdHJhaW50cyAod2hpdGVsaXN0IC8gYmxhY2tsaXN0KQ0KYGBge3IgY29uc3RyYWludHN9DQphbGxfbm9kZXMgPC0gYygiYWdlIiwiZ2VuZGVyIiwiaHlwZXJ0ZW5zaW9uIiwiaGVhcnRfZGlzZWFzZSIsImV2ZXJfbWFycmllZCIsDQogICAgICAgICAgICAgICAid29ya190eXBlIiwiUmVzaWRlbmNlX3R5cGUiLCJhdmdfZ2x1Y29zZV9sZXZlbCIsImJtaSIsDQogICAgICAgICAgICAgICAic21va2luZ19zdGF0dXMiLCJzdHJva2UiKQ0KZGlzY3JldGVfbm9kZXMgPC0gc2V0ZGlmZihhbGxfbm9kZXMsIGMoImFnZSIsImF2Z19nbHVjb3NlX2xldmVsIiwiYm1pIiwic3Ryb2tlIikpDQoNCiMgRGlzY3JldGUgbW9kZWxzOiBjbGluaWNhbGx5IHN1cHBvcnRlZCBwcmVkaWN0b3JzIHBvaW50IElOVE8gc3Ryb2tlLg0Kd2xfZGlzY3JldGUgPC0gbWF0cml4KGMoImFnZSIsInN0cm9rZSIsImhlYXJ0X2Rpc2Vhc2UiLCJzdHJva2UiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImh5cGVydGVuc2lvbiIsInN0cm9rZSIsImF2Z19nbHVjb3NlX2xldmVsIiwic3Ryb2tlIiksDQogICAgICAgICAgICAgICAgICAgICAgbmNvbD0yLCBieXJvdz1UUlVFLCBkaW1uYW1lcz1saXN0KE5VTEwsYygiZnJvbSIsInRvIikpKQ0KYmxfZGlzY3JldGUgPC0gYmluZF9yb3dzKA0KICBleHBhbmQuZ3JpZChmcm9tPWFsbF9ub2RlcywgdG89YygiYWdlIiwiZ2VuZGVyIiksIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpICU+JSBmaWx0ZXIoZnJvbSE9dG8pLA0KICBleHBhbmQuZ3JpZChmcm9tPSJzdHJva2UiLCAgdG89YWxsX25vZGVzLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKSAlPiUgZmlsdGVyKHRvIT0ic3Ryb2tlIiksDQogIGRhdGEuZnJhbWUoZnJvbT1jKCJnZW5kZXIiLCJSZXNpZGVuY2VfdHlwZSIpLCB0bz1jKCJzdHJva2UiLCJzdHJva2UiKSkNCikgJT4lIGFzLm1hdHJpeCgpOyBjb2xuYW1lcyhibF9kaXNjcmV0ZSkgPC0gYygiZnJvbSIsInRvIikNCg0KIyBIeWJyaWQgbW9kZWxzOiBDR0JOIGZvcmJpZHMgY29udGludW91cy0+ZGlzY3JldGUsIHNvIGFnZS9nbHVjb3NlIGFyY3MgcmV2ZXJzZS4NCndsX2h5YnJpZCA8LSBtYXRyaXgoYygic3Ryb2tlIiwiYWdlIiwiaGVhcnRfZGlzZWFzZSIsInN0cm9rZSIsDQogICAgICAgICAgICAgICAgICAgICAgImh5cGVydGVuc2lvbiIsInN0cm9rZSIsInN0cm9rZSIsImF2Z19nbHVjb3NlX2xldmVsIiksDQogICAgICAgICAgICAgICAgICAgIG5jb2w9MiwgYnlyb3c9VFJVRSwgZGltbmFtZXM9bGlzdChOVUxMLGMoImZyb20iLCJ0byIpKSkNCmJsX2h5YnJpZCA8LSBiaW5kX3Jvd3MoDQogIGV4cGFuZC5ncmlkKGZyb209YWxsX25vZGVzLCB0bz0iZ2VuZGVyIiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkgJT4lIGZpbHRlcihmcm9tIT10byksDQogIGV4cGFuZC5ncmlkKGZyb209InN0cm9rZSIsICB0bz1kaXNjcmV0ZV9ub2Rlcywgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkgJT4lIGZpbHRlcih0byE9InN0cm9rZSIpLA0KICBkYXRhLmZyYW1lKGZyb209YygiZ2VuZGVyIiwiUmVzaWRlbmNlX3R5cGUiKSwgdG89Yygic3Ryb2tlIiwic3Ryb2tlIikpDQopICU+JSBhcy5tYXRyaXgoKTsgY29sbmFtZXMoYmxfaHlicmlkKSA8LSBjKCJmcm9tIiwidG8iKQ0KY2F0KCJXaGl0ZWxpc3RzL2JsYWNrbGlzdHMgcmVhZHkuXG4iKQ0KYGBgDQoNCiMgMDggIENvcmUgZXZhbHVhdGlvbiBmdW5jdGlvbg0KYGBge3IgZXZhbC1mbn0NCiMgVHJhaW5zIG9uZSBCTiBhcm0sIHR1bmVzIHRoZSB0aHJlc2hvbGQgb24gVkFMSURBVElPTiwgc2NvcmVzIGEgYmxpbmQgVEVTVCBzZXQuDQojIFN0cnVjdHVyZSBsZWFybmluZzogaGlsbC1jbGltYmluZyAoSEMpLiBTZXQgYm9vdF9SPjAgZm9yIGFyYy1zdHJlbmd0aCBib290c3RyYXAuDQpldmFsdWF0ZV92YXJpYW50IDwtIGZ1bmN0aW9uKHRyYWluX2RmLCB2YWxfZGYsIHRlc3RfZGYsIG1vZGVsX25hbWUsIGJvb3RfUiA9IDIwMCkgew0KICBpc19kaXNjIDwtIGFsbChzYXBwbHkodHJhaW5fZGYsIGlzLmZhY3RvcikpDQogIHdsIDwtIGlmIChpc19kaXNjKSB3bF9kaXNjcmV0ZSBlbHNlIHdsX2h5YnJpZA0KICBibCA8LSBpZiAoaXNfZGlzYykgYmxfZGlzY3JldGUgZWxzZSBibF9oeWJyaWQNCg0KICBkYWcgPC0gaGModHJhaW5fZGYsIHdoaXRlbGlzdCA9IHdsLCBibGFja2xpc3QgPSBibCkNCiAgYXJjX3N0ciA8LSBpZiAoYm9vdF9SID4gMCkNCiAgICBib290LnN0cmVuZ3RoKHRyYWluX2RmLCBSID0gYm9vdF9SLCBhbGdvcml0aG0gPSAiaGMiLA0KICAgICAgICAgICAgICAgICAgYWxnb3JpdGhtLmFyZ3MgPSBsaXN0KHdoaXRlbGlzdCA9IHdsLCBibGFja2xpc3QgPSBibCkpIGVsc2UgTlVMTA0KICBmaXR0ZWQgPC0gaWYgKGlzX2Rpc2MpIGJuLmZpdChkYWcsIHRyYWluX2RmLCBtZXRob2QgPSAiYmF5ZXMiLCBpc3MgPSAxMCkgZWxzZSBibi5maXQoZGFnLCB0cmFpbl9kZikNCg0KICAjIFBvc3RlcmlvciBQKHN0cm9rZT0xKTogZXhhY3QgZm9yIGRpc2NyZXRlLCBsaWtlbGlob29kLXdlaWdodGluZyBmb3IgaHlicmlkLg0KICBnZXRfcHJvYnMgPC0gZnVuY3Rpb24odGQpIHsNCiAgICBpZiAoaXNfZGlzYykgew0KICAgICAgcHAgPC0gcHJlZGljdChmaXR0ZWQsIG5vZGU9InN0cm9rZSIsIGRhdGE9dGQsIG1ldGhvZD0iYmF5ZXMtbHciLCBwcm9iPVRSVUUpDQogICAgICByZXR1cm4oYXR0cihwcCwicHJvYiIpWyIxIixdKQ0KICAgIH0NCiAgICBldl9jb2xzIDwtIHNldGRpZmYobmFtZXModGQpLCAic3Ryb2tlIikNCiAgICB2YXBwbHkoc2VxX2xlbihucm93KHRkKSksIGZ1bmN0aW9uKGkpew0KICAgICAgcCA8LSBzdXBwcmVzc1dhcm5pbmdzKGNwcXVlcnkoZml0dGVkLCBldmVudD0oc3Ryb2tlPT0iMSIpLA0KICAgICAgICAgICAgIGV2aWRlbmNlPWFzLmxpc3QodGRbaSwgZXZfY29sc10pLCBtZXRob2Q9Imx3Iiwgbj0xMDAwKSkNCiAgICAgIGlmIChpcy5uYShwKSkgMCBlbHNlIHAgfSwgbnVtZXJpYygxKSkNCiAgfQ0KDQogIHZhbF9wcm9icyAgPC0gZ2V0X3Byb2JzKHZhbF9kZikNCiAgcm9jX3ZhbCAgICA8LSByb2ModmFsX2RmJHN0cm9rZSwgdmFsX3Byb2JzLCBsZXZlbHM9YygiMCIsIjEiKSwgcXVpZXQ9VFJVRSkNCiAgb3B0X3RocmVzaCA8LSBjb29yZHMocm9jX3ZhbCwgImJlc3QiLCByZXQ9InRocmVzaG9sZCIsIGJlc3QubWV0aG9kPSJ5b3VkZW4iKSR0aHJlc2hvbGRbMV0NCiAgaWYgKGlzLm5hKG9wdF90aHJlc2gpKSBvcHRfdGhyZXNoIDwtIDAuNQ0KDQogIHRlc3RfcHJvYnMgPC0gZ2V0X3Byb2JzKHRlc3RfZGYpDQogIHJvY190ZXN0ICAgPC0gcm9jKHRlc3RfZGYkc3Ryb2tlLCB0ZXN0X3Byb2JzLCBsZXZlbHM9YygiMCIsIjEiKSwgcXVpZXQ9VFJVRSkNCiAgdGVzdF9wcmVkcyA8LSBmYWN0b3IoaWZlbHNlKHRlc3RfcHJvYnMgPj0gb3B0X3RocmVzaCwgIjEiLCAiMCIpLCBsZXZlbHM9YygiMCIsIjEiKSkNCiAgY20gPC0gY29uZnVzaW9uTWF0cml4KHRlc3RfcHJlZHMsIHRlc3RfZGYkc3Ryb2tlLCBwb3NpdGl2ZT0iMSIpDQoNCiAgbGlzdCgNCiAgICByZXN1bHRzID0gZGF0YS5mcmFtZShNb2RlbD1tb2RlbF9uYW1lLCBWYWxfVGhyZXNob2xkPXJvdW5kKG9wdF90aHJlc2gsNCksDQogICAgICBUZXN0X0FVQz1yb3VuZChhcy5udW1lcmljKGF1Yyhyb2NfdGVzdCkpLDQpLA0KICAgICAgVGVzdF9TZW5zaXRpdml0eT1yb3VuZChjbSRieUNsYXNzWyJTZW5zaXRpdml0eSJdLDQpLA0KICAgICAgVGVzdF9TcGVjaWZpY2l0eT1yb3VuZChjbSRieUNsYXNzWyJTcGVjaWZpY2l0eSJdLDQpLA0KICAgICAgVGVzdF9Zb3VkZW5fSj1yb3VuZChjbSRieUNsYXNzWyJTZW5zaXRpdml0eSJdK2NtJGJ5Q2xhc3NbIlNwZWNpZmljaXR5Il0tMSw0KSwNCiAgICAgIFRlc3RfRjE9cm91bmQoYXMubnVtZXJpYyhjbSRieUNsYXNzWyJGMSJdKSw0KSksDQogICAgcm9jPXJvY190ZXN0LCBmaXQ9Zml0dGVkLCBzdHJ1Y3R1cmU9ZGFnLCBhcmNfc3RyZW5ndGg9YXJjX3N0ciwNCiAgICBjb25mdXNpb25fbWF0cml4PWNtLCB0ZXN0X3Byb2JzPXRlc3RfcHJvYnMpDQp9DQpgYGANCg0KIyAwOSAgTUFJTiBSRVNVTFQg4oCUIHRyYWluICYgcmFuayB0aGUgOCBhcm1zDQpgYGB7ciBtYWluLWxvb3B9DQpleHBlcmltZW50cyA8LSBsaXN0KA0KICBsaXN0KHRyYWluPXRyYWluX20xLHZhbD12YWxfbTEsdGVzdD10ZXN0X20xLG5hbWU9Ik0xOiBCYXNlbGluZSIpLA0KICBsaXN0KHRyYWluPXRyYWluX20yLHZhbD12YWxfbTIsdGVzdD10ZXN0X20yLG5hbWU9Ik0yOiBNSUNFICsgRGlzY3JldGl6ZSIpLA0KICBsaXN0KHRyYWluPXRyYWluX20zLHZhbD12YWxfbTMsdGVzdD10ZXN0X20zLG5hbWU9Ik0zOiBDb250aW51b3VzIE5vZGVzIiksDQogIGxpc3QodHJhaW49dHJhaW5fbTQsdmFsPXZhbF9tNCx0ZXN0PXRlc3RfbTQsbmFtZT0iTTQ6IFNNT1RFIG9uIEJhc2VsaW5lIiksDQogIGxpc3QodHJhaW49dHJhaW5fbTUsdmFsPXZhbF9tNSx0ZXN0PXRlc3RfbTUsbmFtZT0iTTU6IEFMTCAoTUlDRSArIENvbnQgKyBTTU9URSkiKSwNCiAgbGlzdCh0cmFpbj10cmFpbl9tNix2YWw9dmFsX202LHRlc3Q9dGVzdF9tNixuYW1lPSJNNjogTUlDRSArIENvbnRpbnVvdXMiKSwNCiAgbGlzdCh0cmFpbj10cmFpbl9tNyx2YWw9dmFsX203LHRlc3Q9dGVzdF9tNyxuYW1lPSJNNzogQ29udGludW91cyArIFNNT1RFIiksDQogIGxpc3QodHJhaW49dHJhaW5fbTgsdmFsPXZhbF9tOCx0ZXN0PXRlc3RfbTgsbmFtZT0iTTg6IE1JQ0UgKyBTTU9URSIpDQopDQoNCmFibGF0aW9uX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZSgpDQpyb2NfbGlzdCA8LSBsaXN0KCk7IGRhZ3NfbGlzdCA8LSBsaXN0KCk7IGFyY19zdHJfbGlzdCA8LSBsaXN0KCkNCmNtX2xpc3QgPC0gbGlzdCgpOyBmaXRfbGlzdCA8LSBsaXN0KCkNCg0KZm9yIChleCBpbiBleHBlcmltZW50cykgew0KICB0cnlDYXRjaCh7DQogICAgciA8LSBldmFsdWF0ZV92YXJpYW50KGV4JHRyYWluLCBleCR2YWwsIGV4JHRlc3QsIGV4JG5hbWUsIGJvb3RfUiA9IDIwMCkNCiAgICBhYmxhdGlvbl9yZXN1bHRzIDwtIHJiaW5kKGFibGF0aW9uX3Jlc3VsdHMsIHIkcmVzdWx0cykNCiAgICByb2NfbGlzdFtbZXgkbmFtZV1dIDwtIHIkcm9jOyBkYWdzX2xpc3RbW2V4JG5hbWVdXSA8LSByJHN0cnVjdHVyZQ0KICAgIGFyY19zdHJfbGlzdFtbZXgkbmFtZV1dIDwtIHIkYXJjX3N0cmVuZ3RoOyBjbV9saXN0W1tleCRuYW1lXV0gPC0gciRjb25mdXNpb25fbWF0cml4DQogICAgZml0X2xpc3RbW2V4JG5hbWVdXSA8LSByJGZpdA0KICB9LCBlcnJvcj1mdW5jdGlvbihlKSBjYXQoc3ByaW50ZigiRXJyb3IgaW4gJXM6ICVzXG4iLCBleCRuYW1lLCBlJG1lc3NhZ2UpKSkNCn0NCg0KIyA5NSUgQ0kgZm9yIEFVQyAoYm9vdHN0cmFwKSwgdGhlbiBSQU5LIEJZIEFVQyAocHJpbWFyeSksIFlvdWRlbidzIEogKHNlY29uZGFyeSkuDQptYXN0ZXJfbGVhZGVyYm9hcmQgPC0gYWJsYXRpb25fcmVzdWx0cw0KbWFzdGVyX2xlYWRlcmJvYXJkJFRlc3RfQVVDXzk1X0NJIDwtIHZhcHBseShtYXN0ZXJfbGVhZGVyYm9hcmQkTW9kZWwsIGZ1bmN0aW9uKG0pew0KICBjaSA8LSBjaS5hdWMocm9jX2xpc3RbW21dXSwgbWV0aG9kPSJib290c3RyYXAiLCBib290Lm49MjAwMCwgcXVpZXQ9VFJVRSkNCiAgc3ByaW50ZigiWyUuNGYgLSAlLjRmXSIsIGNpWzFdLCBjaVszXSkgfSwgY2hhcmFjdGVyKDEpKQ0KDQptYXN0ZXJfbGVhZGVyYm9hcmQgPC0gbWFzdGVyX2xlYWRlcmJvYXJkICU+JQ0KICByZWxvY2F0ZShUZXN0X0FVQ185NV9DSSwgLmFmdGVyID0gVGVzdF9BVUMpICU+JQ0KICBhcnJhbmdlKGRlc2MoVGVzdF9BVUMpLCBkZXNjKFRlc3RfWW91ZGVuX0opKSAgICAgIyA8LS0gQVVDLXByaW1hcnkgcmFua2luZw0KDQpwcmludChtYXN0ZXJfbGVhZGVyYm9hcmQpDQp3cml0ZS5jc3YobWFzdGVyX2xlYWRlcmJvYXJkLCAibGVhZGVyYm9hcmRfZmluYWwuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpgYGANCiNEQUcgc3RydWN0dXJlLCBDUFQgYW5kIGluZmVyZW5jZSB0YWJsZQ0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgU0VHTUVOVCA5LjUgIOKAlCAgTUFOVVNDUklQVCBBUlRJRkFDVFMgKERBRywgQ1BUcywgZXhhbXBsZXMpDQojIEV2ZXJ5dGhpbmcgaXMgZXh0cmFjdGVkIGZyb20gdGhlIG9wdGltYWwgbW9kZWwgTTYgYW5kIHdyaXR0ZW4gdG8NCiMgQ1NWL1BERiBzbyB0aGUgbWFudXNjcmlwdCB0YWJsZXMvZmlndXJlcyB1c2UgZXhhY3QsIHJlcHJvZHVjaWJsZSB2YWx1ZXMuDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KYGBge3IgYXJ0aWZhY3RzLXNldHVwfQ0KbGlicmFyeShibmxlYXJuKTsgbGlicmFyeShkcGx5cikNCkJFU1QgPC0gIk02OiBNSUNFICsgQ29udGludW91cyINCm5ldCAgPC0gZml0X2xpc3RbW0JFU1RdXSAgICAgICAgIyBmaXR0ZWQgaHlicmlkIG5ldHdvcmsNCmRhZyAgPC0gZGFnc19saXN0W1tCRVNUXV0gICAgICAgIyBzdHJ1Y3R1cmUgb25seQ0KYXN0ciA8LSBhcmNfc3RyX2xpc3RbW0JFU1RdXSAgICAjIGJvb3RzdHJhcCBhcmMgc3RyZW5ndGhzDQpjYXQoIlBhcmVudHMgb2Ygc3Ryb2tlOiIsIHBhc3RlKHBhcmVudHMobmV0LCAic3Ryb2tlIiksIGNvbGxhcHNlPSIsICIpLCAiXG4iKQ0KY2F0KCJDaGlsZHJlbiBvZiBzdHJva2U6IiwgcGFzdGUoY2hpbGRyZW4obmV0LCAic3Ryb2tlIiksIGNvbGxhcHNlPSIsICIpLCAiXG4iKQ0KYGBgDQoNCiMjIDkuNWEgIERBRyDigJQgYXJjIGxpc3QsIGJvb3RzdHJhcCBzdHJlbmd0aHMsIGFuZCBmaWd1cmUNCmBgYHtyIGRhZ30NCiMgKGkpIEFyYyBsaXN0IHdpdGggYm9vdHN0cmFwIHN1cHBvcnQgKHN0cmVuZ3RoKSBhbmQgZGlyZWN0aW9uIGNvbmZpZGVuY2UuDQphcmNfdGJsIDwtIGFzdHIgJT4lDQogIGZpbHRlcihzdHJlbmd0aCA+IDAuNTAgJiBkaXJlY3Rpb24gPj0gMC41MCkgJT4lDQogIGFycmFuZ2UoZGVzYyhzdHJlbmd0aCkpDQpwcmludChhcmNfdGJsKQ0Kd3JpdGUuY3N2KGFyY190YmwsICJNNl9hcmNfc3RyZW5ndGhzLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KDQojIChpaSkgUHVibGljYXRpb24gREFHIHdpdGggZWRnZSB0aGlja25lc3MgfiBib290c3RyYXAgc3RyZW5ndGguDQpwZGYoIk02X0RBRy5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDcpDQpzdHJlbmd0aC5wbG90KGRhZywgYXN0ciwgbWFpbiA9ICJNNjogTUlDRSArIENvbnRpbnVvdXMg4oCUIGNvbnNlbnN1cyBEQUciLA0KICAgICAgICAgICAgICBzaGFwZSA9ICJlbGxpcHNlIikNCmRldi5vZmYoKQ0KcHJpbnQoZGFnKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGZ1bGwgc3RydWN0dXJlIHN1bW1hcnkgKG5vZGVzLCBhcmNzLCBwYXJlbnRzL2NoaWxkcmVuKQ0KY2F0KCJNb2RlbCBzdHJpbmc6XG4iLCBtb2RlbHN0cmluZyhkYWcpLCAiXG5cbiIpICAgIyBjb21wYWN0IG9uZS1saW5lIERBRw0KY2F0KCJBcmMgbGlzdCAoZnJvbSAtPiB0byk6XG4iKTsgcHJpbnQoYXJjcyhkYWcpKSAgIyBldmVyeSBkaXJlY3RlZCBlZGdlDQpjYXQoIlxuTWFya292IGJsYW5rZXQgb2Ygc3Ryb2tlOiIsIHBhc3RlKG1iKGRhZywgInN0cm9rZSIpLCBjb2xsYXBzZSA9ICIsICIpLCAiXG4iKQ0KY2F0KCJTYXZlZCBNNl9EQUcucGRmXG4iKQ0KDQojIChpaWkpIE9wdGlvbmFsIGludGVyYWN0aXZlIERBRyANCiBsaWJyYXJ5KHZpc05ldHdvcmspDQogbm9kZXMgPC0gZGF0YS5mcmFtZShpZCA9IG5vZGVzKGRhZyksIGxhYmVsID0gbm9kZXMoZGFnKSkNCiBlZGdlcyA8LSBkYXRhLmZyYW1lKGZyb20gPSBhcmNzKGRhZylbLDFdLCB0byA9IGFyY3MoZGFnKVssMl0sIGFycm93cyA9ICJ0byIpDQogdmlzTmV0d29yayhub2RlcywgZWRnZXMpICU+JSB2aXNFZGdlcyhzbW9vdGggPSBGQUxTRSkNCmBgYA0KDQojIyA5LjViICBUYWJsZSA1IOKAlCBDUFQgb2Ygc3Ryb2tlIChkaXNjcmV0ZSBwYXJlbnRzKQ0KYGBge3IgY3B0LXN0cm9rZX0NCiMgU3Ryb2tlIGlzIGRpc2NyZXRlIHdpdGggZGlzY3JldGUgcGFyZW50cyAoQ0dCTiBmb3JiaWRzIGNvbnRpbnVvdXMgcGFyZW50cyksDQojIHNvIGl0cyBDUFQgaXMgYSBjbGVhbiBwcm9iYWJpbGl0eSB0YWJsZS4gV2UgdGlkeSBpdCBhbmQgZmxhZyB0aGUga2V5IHJvd3MuDQpzdHJva2VfY3B0IDwtIGFzLmRhdGEuZnJhbWUudGFibGUoY29lZihuZXQkc3Ryb2tlKSwgcmVzcG9uc2VOYW1lID0gInByb2IiKQ0KDQojIEtlZXAgUChzdHJva2UgPSAxIHwgcGFyZW50cykgYW5kIG9yZGVyIGJ5IHJpc2suDQpyaXNrMSA8LSBzdHJva2VfY3B0ICU+JSBmaWx0ZXIoc3Ryb2tlID09ICIxIikgJT4lDQogIG11dGF0ZShwcm9iX3BjdCA9IHJvdW5kKDEwMCAqIHByb2IsIDIpKSAlPiUNCiAgYXJyYW5nZShkZXNjKHByb2IpKSAlPiUgc2VsZWN0KC1zdHJva2UpDQpwcmludChyaXNrMSkNCndyaXRlLmNzdihyaXNrMSwgIk02X0NQVF9zdHJva2UuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQoNCiMgQXV0by1leHRyYWN0IHRoZSB3b3JrZWQgZXhhbXBsZXMgdGhlIG1hbnVzY3JpcHQgcXVvdGVzOg0KY2F0KCJcbi0tLSBXb3JrZWQgQ1BUIGV4YW1wbGVzIChleGFjdCB2YWx1ZXMgZm9yIHRoZSB0ZXh0KSAtLS1cbiIpDQpjYXQoc3ByaW50ZigiSGlnaGVzdC1yaXNrIHBhcmVudCBjb21iaW5hdGlvbjogJS4yZiUlXG4iLCBtYXgocmlzazEkcHJvYl9wY3QpKSkNCmNhdChzcHJpbnRmKCJMb3dlc3QtcmlzayBwYXJlbnQgY29tYmluYXRpb246ICAlLjJmJSVcbiIsIG1pbihyaXNrMSRwcm9iX3BjdCkpKQ0KIyBQcmludCB0aGUgZnVsbCBsYWJlbCBvZiB0aGUgaGlnaGVzdC1yaXNrIGNlbGwgc28geW91IGNhbiBkZXNjcmliZSBpdCBwcmVjaXNlbHk6DQp0b3AgPC0gcmlzazFbd2hpY2gubWF4KHJpc2sxJHByb2JfcGN0KSwgXQ0KY2F0KCJIaWdoZXN0LXJpc2sgY2VsbDpcbiIpOyBwcmludCh0b3ApDQpgYGANCg0KIyMgOS41YyAgVGFibGUgNiDigJQgY29udGludW91cyBjaGlsZHJlbiBvZiBzdHJva2UgKEdhdXNzaWFuIHBhcmFtZXRlcnMpDQpgYGB7ciBjcHQtY29udGludW91c30NCiMgRm9yIGVhY2ggY29udGludW91cyBjaGlsZCBvZiBzdHJva2UsIHJlcG9ydCB0aGUgbG9jYWwgcmVncmVzc2lvbiBjb2VmZmljaWVudHMNCiMgYW5kIHJlc2lkdWFsIFNEIHBlciBkaXNjcmV0ZS1wYXJlbnQgY29uZmlndXJhdGlvbiAodGhpcyBpcyB0aGUgQ0cgcGFyYW1ldGVyaXNhdGlvbikuDQpjb250X2NoaWxkcmVuIDwtIGludGVyc2VjdChjaGlsZHJlbihuZXQsICJzdHJva2UiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoImFnZSIsICJhdmdfZ2x1Y29zZV9sZXZlbCIsICJibWkiKSkNCg0KZm9yIChuZCBpbiBjb250X2NoaWxkcmVuKSB7DQogIGNhdCgiXG49PT09IENvbnRpbnVvdXMgbm9kZToiLCBuZCwgIj09PT1cbiIpDQogIGNhdCgiQ29lZmZpY2llbnRzIChpbnRlcmNlcHQvc2xvcGVzIHBlciBwYXJlbnQgY29uZmlndXJhdGlvbik6XG4iKQ0KICBwcmludChuZXRbW25kXV0kY29lZmZpY2llbnRzKQ0KICBjYXQoIlJlc2lkdWFsIFNEIHBlciBjb25maWd1cmF0aW9uOlxuIikNCiAgcHJpbnQobmV0W1tuZF1dJHNkKQ0KICAjIFRpZHkgZXhwb3J0DQogIGNvIDwtIGFzLmRhdGEuZnJhbWUobmV0W1tuZF1dJGNvZWZmaWNpZW50cykNCiAgY28kdGVybSA8LSByb3duYW1lcyhjbykNCiAgd3JpdGUuY3N2KGNvLCBzcHJpbnRmKCJNNl9wYXJhbXNfJXNfY29lZi5jc3YiLCBuZCksIHJvdy5uYW1lcyA9IEZBTFNFKQ0KICB3cml0ZS5jc3YoZGF0YS5mcmFtZShjb25maWcgPSBuYW1lcyhuZXRbW25kXV0kc2QpLCBzZCA9IGFzLm51bWVyaWMobmV0W1tuZF1dJHNkKSksDQogICAgICAgICAgICBzcHJpbnRmKCJNNl9wYXJhbXNfJXNfc2QuY3N2IiwgbmQpLCByb3cubmFtZXMgPSBGQUxTRSkNCn0NCmNhdCgiXG5TYXZlZCBwZXItbm9kZSBjb2VmZmljaWVudCBhbmQgU0QgQ1NWcy5cbiIpDQpgYGANCg0KIyMgOS41ZCAgSW5mZXJlbmNlICYgY291bnRlcmZhY3R1YWwgZXhhbXBsZXMgKFRhYmxlIGZvciB0aGUgdGV4dCkNCmBgYHtyIGluZmVyZW5jZS10YWJsZX0NCiMgRXhhY3QsIHJlcHJvZHVjaWJsZSBwcm9iYWJpbGl0aWVzIGZvciB0aGUgY2xpbmljYWwgcGVyc29uYXMgKyBpbnRlcnZlbnRpb24uDQpzZXQuc2VlZCgyMDI2KTsgbl9zaW0gPC0gMWU2ICAgIyBsb2dpYyBzYW1wbGluZw0KDQpwQSA8LSBjcHF1ZXJ5KG5ldCwgZXZlbnQ9KHN0cm9rZT09IjEiKSwNCiAgICAgICAgICAgICAgZXZpZGVuY2U9KGFnZTw9NTAgJiBoeXBlcnRlbnNpb249PSIwIiAmIGhlYXJ0X2Rpc2Vhc2U9PSIwIiksIG49bl9zaW0pDQpwQiA8LSBjcHF1ZXJ5KG5ldCwgZXZlbnQ9KHN0cm9rZT09IjEiKSwNCiAgICAgICAgICAgICAgZXZpZGVuY2U9KGFnZT49NjUgJiBoeXBlcnRlbnNpb249PSIxIiAmIGhlYXJ0X2Rpc2Vhc2U9PSIxIiAmIGF2Z19nbHVjb3NlX2xldmVsPj0xNTApLCBuPW5fc2ltKQ0KcEMgPC0gY3BxdWVyeShuZXQsIGV2ZW50PShzdHJva2U9PSIxIiksDQogICAgICAgICAgICAgIGV2aWRlbmNlPShhZ2U+PTY1ICYgaHlwZXJ0ZW5zaW9uPT0iMSIgJiBoZWFydF9kaXNlYXNlPT0iMSIgJiBhdmdfZ2x1Y29zZV9sZXZlbDw9MTAwKSwgbj1uX3NpbSkNCg0KaW5mZXJlbmNlX3RibCA8LSBkYXRhLmZyYW1lKA0KICBTY2VuYXJpbyA9IGMoIkE6IExvdy1yaXNrIChhZ2U8PTUwLCBubyBIVE4sIG5vIEhEKSIsDQogICAgICAgICAgICAgICAiQjogSGlnaC1yaXNrIChhZ2U+PTY1LCBIVE4sIEhELCBnbHVjb3NlPj0xNTApIiwNCiAgICAgICAgICAgICAgICJDOiBCICsgZ2x5Y2FlbWljIGNvbnRyb2wgKGdsdWNvc2U8PTEwMCkiKSwNCiAgU3Ryb2tlX1Byb2JhYmlsaXR5X3BjdCA9IHJvdW5kKDEwMCAqIGMocEEsIHBCLCBwQyksIDIpKQ0KcHJpbnQoaW5mZXJlbmNlX3RibCkNCmNhdChzcHJpbnRmKCJcbkFic29sdXRlIHJpc2sgcmVkdWN0aW9uIChCIC0+IEMpOiAlLjJmJSUgLT4gJS4yZiUlIChkZWx0YSA9ICUuMmYgcG9pbnRzKVxuIiwNCiAgICAgICAgICAgIDEwMCpwQiwgMTAwKnBDLCAxMDAqKHBCIC0gcEMpKSkNCndyaXRlLmNzdihpbmZlcmVuY2VfdGJsLCAiTTZfaW5mZXJlbmNlX2V4YW1wbGVzLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KDQojIEJhY2t3YXJkIChkaWFnbm9zdGljKSBpbmZlcmVuY2U6IGV4cGVjdGVkIGdsdWNvc2UgZ2l2ZW4gYSBzdHJva2Ugb2NjdXJyZWQuDQpzZXQuc2VlZCg0MikNCnBvc3QgPC0gY3BkaXN0KG5ldCwgbm9kZXMgPSAiYXZnX2dsdWNvc2VfbGV2ZWwiLA0KICAgICAgICAgICAgICAgZXZpZGVuY2UgPSBsaXN0KHN0cm9rZSA9IGZhY3RvcigiMSIsIGxldmVscyA9IGxldmVscyh0cmFpbl9tNiRzdHJva2UpKSksDQogICAgICAgICAgICAgICBtZXRob2QgPSAibHciLCBuID0gMWU1KQ0KY2F0KHNwcmludGYoIkV4cGVjdGVkIGF2ZyBnbHVjb3NlIGFtb25nIHN0cm9rZSBjYXNlcyAobW9kZWwpOiAlLjJmIG1nL2RMXG4iLA0KICAgICAgICAgICAgbWVhbihwb3N0JGF2Z19nbHVjb3NlX2xldmVsLCBuYS5ybSA9IFRSVUUpKSkNCmBgYA0KDQoNCiMgMTAgIFJPQyBmaWd1cmUNCmBgYHtyIHJvYy1maWd9DQpjYlBhbGV0dGUgPC0gYygiIzk5OTk5OSIsIiNFNjlGMDAiLCIjNTZCNEU5IiwiIzAwOUU3MyIsIiNGMEU0NDIiLCIjMDA3MkIyIiwiI0Q1NUUwMCIsIiNDQzc5QTciKQ0KIyBQbG90IG9uIGEgRElTUExBWSBDT1BZIHNvIHJvY19saXN0IGtlZXBzIGl0cyBmdWxsIG1vZGVsIG5hbWVzIChuZWVkZWQgaW4gU2VnIDEzKS4NCnJvY19wbG90IDwtIHJvY19saXN0DQpuYW1lcyhyb2NfcGxvdCkgPC0gZ3N1YigiIFxcKC4qXFwpIiwgIiIsIG5hbWVzKHJvY19wbG90KSkNCnAgPC0gcFJPQzo6Z2dyb2Mocm9jX3Bsb3QsIGxlZ2FjeS5heGVzPVRSVUUsIHNpemU9MS4wKSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdD0wLCBzbG9wZT0xLCBjb2xvdXI9ImRhcmtncmF5IiwgbGluZXR5cGU9ImRhc2hlZCIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jYlBhbGV0dGUpICsNCiAgbGFicyh4PSJGYWxzZSBQb3NpdGl2ZSBSYXRlICgxIC0gU3BlY2lmaWNpdHkpIiwgeT0iVHJ1ZSBQb3NpdGl2ZSBSYXRlIChTZW5zaXRpdml0eSkiLA0KICAgICAgIHRpdGxlPSJNdWx0aS1tb2RlbCBST0MgY29tcGFyaXNvbiIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uPWMoMC44MCwwLjIwKSkNCmdnc2F2ZSgiU3Ryb2tlX1ByZWRpY3Rpb25fTXVsdGlfUk9DLnBuZyIsIHAsIHdpZHRoPTgsIGhlaWdodD02LCBkcGk9MzAwKQ0KcHJpbnQocCkNCmBgYA0KDQojIDExICBDYWxpYnJhdGlvbiAmIEJyaWVyIHNjb3JlIA0KYGBge3IgY2FsaWJyYXRpb259DQojIEJyaWVyIHNjb3JlID0gbWVhbigocCAtIHkpXjIpOiBsb3dlciBpcyBiZXR0ZXItY2FsaWJyYXRlZC4gRGVtb25zdHJhdGVzIHRoZQ0KIyBjbGFpbSB0aGF0IFNNT1RFIGRpc3RvcnRzIHByb2JhYmlsaXRpZXMgKGl0IHNob3VsZCBJTkZMQVRFIHRoZSBCcmllciBzY29yZSkuDQpicmllciA8LSBmdW5jdGlvbihtKXsNCiAgeSA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcihyb2NfbGlzdFtbbV1dJHJlc3BvbnNlKSkNCiAgcCA8LSByb2NfbGlzdFtbbV1dJHByZWRpY3Rvcg0KICBtZWFuKChwIC0geSleMikNCn0NCmJyaWVyX3RibCA8LSBkYXRhLmZyYW1lKE1vZGVsPW5hbWVzKHJvY19saXN0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgIEJyaWVyPXJvdW5kKHZhcHBseShuYW1lcyhyb2NfbGlzdCksIGJyaWVyLCBudW1lcmljKDEpKSw0KSkgJT4lDQogIGFycmFuZ2UoQnJpZXIpDQpwcmludChicmllcl90YmwpOyB3cml0ZS5jc3YoYnJpZXJfdGJsLCAiYnJpZXJfc2NvcmVzLmNzdiIsIHJvdy5uYW1lcz1GQUxTRSkNCg0KIyBSZWxpYWJpbGl0eSBjdXJ2ZSBmb3IgdGhlIGZvdXIgaGVhZGxpbmUgYXJtcy4NCiMgVGllLXJvYnVzdCBiaW5uaW5nOiBkaXNjcmV0ZS9TTU9URSBtb2RlbHMgcHJvZHVjZSBtYW55IGlkZW50aWNhbCBwcm9iYWJpbGl0aWVzLA0KIyBzbyBxdWFudGlsZSBjdXQtcG9pbnRzIGNvbGxhcHNlLiBXZSB1c2UgVU5JUVVFIHF1YW50aWxlIGJyZWFrcyBhbmQgZmFsbCBiYWNrIHRvDQojIGJpbm5pbmcgYnkgZGlzdGluY3QgcHJvYmFiaWxpdHkgdmFsdWVzIHdoZW4gdGllcyBhcmUgc2V2ZXJlLg0KY2FsaWJfY3VydmUgPC0gZnVuY3Rpb24obSwgYmlucz0xMCl7DQogIHkgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIocm9jX2xpc3RbW21dXSRyZXNwb25zZSkpOyBwIDwtIHJvY19saXN0W1ttXV0kcHJlZGljdG9yDQogIGJyIDwtIHVuaXF1ZShxdWFudGlsZShwLCBwcm9icz1zZXEoMCwxLGxlbmd0aC5vdXQ9YmlucysxKSwgbmEucm09VFJVRSkpDQogIGlmIChsZW5ndGgoYnIpIDwgMykgeyAgICAgICAgICAgICAgICAgICAgICAjIHRvbyBtYW55IHRpZXMgLT4gYmluIGJ5IHZhbHVlDQogICAgZyA8LSBjdXQocCwgYnJlYWtzPXVuaXF1ZShjKC1JbmYsIHNvcnQodW5pcXVlKHApKSkpLCBpbmNsdWRlLmxvd2VzdD1UUlVFKQ0KICB9IGVsc2Ugew0KICAgIGcgPC0gY3V0KHAsIGJyZWFrcz1iciwgaW5jbHVkZS5sb3dlc3Q9VFJVRSkNCiAgfQ0KICBvdXQgPC0gZGF0YS5mcmFtZShtb2RlbD1tLCBwcmVkPXRhcHBseShwLCBnLCBtZWFuKSwgb2JzPXRhcHBseSh5LCBnLCBtZWFuKSkNCiAgbmEub21pdChvdXQpDQp9DQpmb2N1cyA8LSBpbnRlcnNlY3QoYygiTTE6IEJhc2VsaW5lIiwiTTM6IENvbnRpbnVvdXMgTm9kZXMiLCJNNjogTUlDRSArIENvbnRpbnVvdXMiLA0KICAgICAgICAgICAgICAgICAgICAgIk01OiBBTEwgKE1JQ0UgKyBDb250ICsgU01PVEUpIiksIG5hbWVzKHJvY19saXN0KSkNCmNhbCA8LSBiaW5kX3Jvd3MobGFwcGx5KGZvY3VzLCBjYWxpYl9jdXJ2ZSkpDQpnZ3Bsb3QoY2FsLCBhZXMocHJlZCwgb2JzLCBjb2xvdXI9bW9kZWwpKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3VyPSJncmV5NTAiKSArDQogIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsNCiAgbGFicyh4PSJNZWFuIHByZWRpY3RlZCBwcm9iYWJpbGl0eSIsIHk9Ik9ic2VydmVkIHN0cm9rZSBmcmFjdGlvbiIsDQogICAgICAgdGl0bGU9IkNhbGlicmF0aW9uIChyZWxpYWJpbGl0eSkgY3VydmVzIikgKyB0aGVtZV9taW5pbWFsKCkgLT4gY2FsX3Bsb3QNCmdnc2F2ZSgiY2FsaWJyYXRpb25fY3VydmVzLnBuZyIsIGNhbF9wbG90LCB3aWR0aD03LCBoZWlnaHQ9NiwgZHBpPTMwMCkNCnByaW50KGNhbF9wbG90KQ0KYGBgDQoNCiMgMTIgIE9wYXF1ZS1tb2RlbCBiZW5jaG1hcms6IGxvZ2lzdGljIHJlZ3Jlc3Npb24gJiBYR0Jvb3N0IA0KYGBge3IgYmVuY2htYXJrfQ0KIyBTYW1lIHNwbGl0cywgY29udGludW91cyBmZWF0dXJlcy4gUXVhbnRpZmllcyBob3cgY2xvc2UgdGhlIGludGVycHJldGFibGUgQk4NCiMgKEFVQyB+MC44MSkgY29tZXMgdG8gc3RhbmRhcmQgYmxhY2stYm94IG1vZGVscyDigJQgdGhlIHBhcGVyJ3MgbW90aXZhdGluZyB0ZW5zaW9uLg0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHtsaWJyYXJ5KHhnYm9vc3QpfSkNCg0KYmVuY2hfdHIgPC0gdHJhaW5fbTM7IGJlbmNoX3ZhIDwtIHZhbF9tMzsgYmVuY2hfdGUgPC0gdGVzdF9tMyAgICMgY29udGludW91cywgbm8gU01PVEUNCg0KIyAtLS0gTG9naXN0aWMgcmVncmVzc2lvbg0KbHIgPC0gZ2xtKHN0cm9rZSB+IC4sIGRhdGE9YmVuY2hfdHIsIGZhbWlseT1iaW5vbWlhbCkNCmxyX3RlIDwtIHByZWRpY3QobHIsIGJlbmNoX3RlLCB0eXBlPSJyZXNwb25zZSIpDQpscl9hdWMgPC0gYXMubnVtZXJpYyhhdWMocm9jKGJlbmNoX3RlJHN0cm9rZSwgbHJfdGUsIGxldmVscz1jKCIwIiwiMSIpLCBxdWlldD1UUlVFKSkpDQoNCiMgLS0tIFhHQm9vc3QgKHN0YWJsZSB4Z2IudHJhaW4gQVBJOyBjb21wYXRpYmxlIHdpdGggeGdib29zdCAxLngtMy54KQ0KIyBucm91bmRzIGlzIHR1bmVkIGJ5IEVBUkxZIFNUT1BQSU5HIG9uIHRoZSB2YWxpZGF0aW9uIGZvbGQgc28gdGhlIGNvbXBhcmlzb24gaXMNCiMgZmFpciAoYSBmaXhlZCBsYXJnZSBucm91bmRzIG92ZXJmaXRzIG9uIH4yNDkgZXZlbnRzIGFuZCB1bmRlcnN0YXRlcyBYR0Jvb3N0KS4NCm1tX3RyIDwtIG1vZGVsLm1hdHJpeChzdHJva2UgfiAuIC0xLCBkYXRhPWJlbmNoX3RyKQ0KbW1fdmEgPC0gbW9kZWwubWF0cml4KHN0cm9rZSB+IC4gLTEsIGRhdGE9YmVuY2hfdmEpDQptbV90ZSA8LSBtb2RlbC5tYXRyaXgoc3Ryb2tlIH4gLiAtMSwgZGF0YT1iZW5jaF90ZSkNCmZlYXQgIDwtIFJlZHVjZShpbnRlcnNlY3QsIGxpc3QoY29sbmFtZXMobW1fdHIpLCBjb2xuYW1lcyhtbV92YSksIGNvbG5hbWVzKG1tX3RlKSkpDQpkdHIgPC0geGdiLkRNYXRyaXgobW1fdHJbLCBmZWF0LCBkcm9wPUZBTFNFXSwgbGFiZWw9YXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYmVuY2hfdHIkc3Ryb2tlKSkpDQpkdmFsPC0geGdiLkRNYXRyaXgobW1fdmFbLCBmZWF0LCBkcm9wPUZBTFNFXSwgbGFiZWw9YXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYmVuY2hfdmEkc3Ryb2tlKSkpDQpkdGUgPC0geGdiLkRNYXRyaXgobW1fdGVbLCBmZWF0LCBkcm9wPUZBTFNFXSwgbGFiZWw9YXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYmVuY2hfdGUkc3Ryb2tlKSkpDQpwYXJhbXMgPC0gbGlzdChvYmplY3RpdmU9ImJpbmFyeTpsb2dpc3RpYyIsIGV2YWxfbWV0cmljPSJhdWMiLA0KICAgICAgICAgICAgICAgbWF4X2RlcHRoPTMsIGxlYXJuaW5nX3JhdGU9MC4wNSwgc3Vic2FtcGxlPTAuOCwgY29sc2FtcGxlX2J5dHJlZT0wLjgsDQogICAgICAgICAgICAgICBzY2FsZV9wb3Nfd2VpZ2h0PXN1bShiZW5jaF90ciRzdHJva2U9PSIwIikvc3VtKGJlbmNoX3RyJHN0cm9rZT09IjEiKSkNCnNldC5zZWVkKEdMT0JBTF9TRUVEKQ0KeGdiIDwtIHhnYi50cmFpbihwYXJhbXM9cGFyYW1zLCBkYXRhPWR0ciwgbnJvdW5kcz0xMDAwLA0KICAgICAgICAgICAgICAgICB3YXRjaGxpc3Q9bGlzdCh2YWw9ZHZhbCksIGVhcmx5X3N0b3BwaW5nX3JvdW5kcz0yNSwgdmVyYm9zZT0wKQ0KeGdiX3RlIDwtIHByZWRpY3QoeGdiLCBkdGUpDQp4Z2JfYXVjIDwtIGFzLm51bWVyaWMoYXVjKHJvYyhiZW5jaF90ZSRzdHJva2UsIHhnYl90ZSwgbGV2ZWxzPWMoIjAiLCIxIiksIHF1aWV0PVRSVUUpKSkNCg0KYmVuY2hfdGJsIDwtIGRhdGEuZnJhbWUoDQogIE1vZGVsPWMoIkJheWVzaWFuIG5ldHdvcmsgKE02KSIsIkxvZ2lzdGljIHJlZ3Jlc3Npb24iLCJYR0Jvb3N0IiksDQogIFRlc3RfQVVDPXJvdW5kKGMobWFzdGVyX2xlYWRlcmJvYXJkJFRlc3RfQVVDW21hc3Rlcl9sZWFkZXJib2FyZCRNb2RlbD09Ik02OiBNSUNFICsgQ29udGludW91cyJdLA0KICAgICAgICAgICAgICAgICAgIGxyX2F1YywgeGdiX2F1YyksNCkpDQpwcmludChiZW5jaF90YmwpOyB3cml0ZS5jc3YoYmVuY2hfdGJsLCAiYmVuY2htYXJrX29wYXF1ZS5jc3YiLCByb3cubmFtZXM9RkFMU0UpDQpgYGANCg0KIyAxMyAgSHlwb3RoZXNpcyB0ZXN0aW5nIOKAlCBEZUxvbmcgKEFVQykgKyBNY05lbWFyIChzZW5zaXRpdml0eSkNCmBgYHtyIGh5cC10ZXN0c30NCiMgVGhyZXNob2xkcyBhcmUgcmVhZCBmcm9tIHRoZSBsZWFkZXJib2FyZCAobm8gaGFyZC1jb2RpbmcpLg0KdGhyIDwtIGZ1bmN0aW9uKG0pIG1hc3Rlcl9sZWFkZXJib2FyZCRWYWxfVGhyZXNob2xkW21hc3Rlcl9sZWFkZXJib2FyZCRNb2RlbD09bV0NCg0KIyBNY05lbWFyIG9uIHRoZSBhY3R1YWwtc3Ryb2tlIGNvaG9ydDogZGlkIG1vZGVsIEEgY2F0Y2ggY2FzZXMgQiBtaXNzZWQ/DQptY25lbWFyX3NlbnMgPC0gZnVuY3Rpb24oQSwgQil7DQogIHkgIDwtIHJvY19saXN0W1tBXV0kcmVzcG9uc2UNCiAgcGEgPC0gaWZlbHNlKHJvY19saXN0W1tBXV0kcHJlZGljdG9yID49IHRocihBKSwgMSwgMCkNCiAgcGIgPC0gaWZlbHNlKHJvY19saXN0W1tCXV0kcHJlZGljdG9yID49IHRocihCKSwgMSwgMCkNCiAgaWR4IDwtIHdoaWNoKHkgPT0gMSkNCiAgdGFiIDwtIHRhYmxlKEE9ZmFjdG9yKHBhW2lkeF0sMDoxKSwgQj1mYWN0b3IocGJbaWR4XSwwOjEpKQ0KICBjYXQoc3ByaW50ZigiXG5NY05lbWFyIChzZW5zaXRpdml0eSkgJXMgdnMgJXM6XG4iLCBBLCBCKSk7IHByaW50KG1jbmVtYXIudGVzdCh0YWIpKQ0KfQ0KDQpjYXQoIj09PSBIMSBUb3BvbG9neTogY29udGludW91cyB2cyBkaXNjcmV0ZSA9PT1cbiIpDQpwcmludChyb2MudGVzdChyb2NfbGlzdFtbIk0zOiBDb250aW51b3VzIE5vZGVzIl1dLCByb2NfbGlzdFtbIk0xOiBCYXNlbGluZSJdXSwgbWV0aG9kPSJkZWxvbmciKSkNCnByaW50KHJvYy50ZXN0KHJvY19saXN0W1siTTY6IE1JQ0UgKyBDb250aW51b3VzIl1dLCByb2NfbGlzdFtbIk0yOiBNSUNFICsgRGlzY3JldGl6ZSJdXSwgbWV0aG9kPSJkZWxvbmciKSkNCg0KY2F0KCJcbj09PSBIMiBJbXB1dGF0aW9uOiBNSUNFIHZzIG1lZGlhbiA9PT1cbiIpDQpwcmludChyb2MudGVzdChyb2NfbGlzdFtbIk0yOiBNSUNFICsgRGlzY3JldGl6ZSJdXSwgcm9jX2xpc3RbWyJNMTogQmFzZWxpbmUiXV0sIG1ldGhvZD0iZGVsb25nIikpDQpwcmludChyb2MudGVzdChyb2NfbGlzdFtbIk02OiBNSUNFICsgQ29udGludW91cyJdXSwgcm9jX2xpc3RbWyJNMzogQ29udGludW91cyBOb2RlcyJdXSwgbWV0aG9kPSJkZWxvbmciKSkNCg0KY2F0KCJcbj09PSBIMyBCYWxhbmNpbmc6IFNNT1RFIHZzIHVuYWRqdXN0ZWQgKEFVQyArIHNlbnNpdGl2aXR5KSA9PT1cbiIpDQojIEtleSBwb2ludDogU01PVEUgZG9lcyBOT1QgaW1wcm92ZSBBVUMgKERlTG9uZyksIGV2ZW4gd2hlcmUgaXQgcmFpc2VzIHNlbnNpdGl2aXR5Lg0KTTUgPC0gIk01OiBBTEwgKE1JQ0UgKyBDb250ICsgU01PVEUpIiAgICMgZnVsbCBuYW1lLCBtYXRjaGVzIHJvY19saXN0IC8gbGVhZGVyYm9hcmQNCnByaW50KHJvYy50ZXN0KHJvY19saXN0W1siTTQ6IFNNT1RFIG9uIEJhc2VsaW5lIl1dLCByb2NfbGlzdFtbIk0xOiBCYXNlbGluZSJdXSwgbWV0aG9kPSJkZWxvbmciKSkNCnByaW50KHJvYy50ZXN0KHJvY19saXN0W1tNNV1dLCByb2NfbGlzdFtbIk02OiBNSUNFICsgQ29udGludW91cyJdXSwgbWV0aG9kPSJkZWxvbmciKSkNCm1jbmVtYXJfc2VucyhNNSwgIk02OiBNSUNFICsgQ29udGludW91cyIpDQoNCmNhdCgiXG49PT0gSDQgU3luZXJneTogYmVzdCBjb250aW51b3VzIHZzIGFic29sdXRlIGJhc2VsaW5lID09PVxuIikNCnByaW50KHJvYy50ZXN0KHJvY19saXN0W1siTTY6IE1JQ0UgKyBDb250aW51b3VzIl1dLCByb2NfbGlzdFtbIk0xOiBCYXNlbGluZSJdXSwgbWV0aG9kPSJkZWxvbmciKSkNCmBgYA0KIzEzYiBIeXBvdGhlc2lzIHRlc3QgYW5kIHAtdmFsdWUgcmUtcnVuDQpgYGB7cn0NCmxpYnJhcnkocFJPQykNCg0KIyBWYWxpZGF0aW9uLXR1bmVkIHRocmVzaG9sZCBmb3IgZWFjaCBtb2RlbCAocmVhZCBmcm9tIHRoZSBsZWFkZXJib2FyZCkuDQp0aHIgPC0gZnVuY3Rpb24obSkgbWFzdGVyX2xlYWRlcmJvYXJkJFZhbF9UaHJlc2hvbGRbbWFzdGVyX2xlYWRlcmJvYXJkJE1vZGVsID09IG1dDQoNCiMgTWNOZW1hciBvbiB0aGUgYWN0dWFsLXN0cm9rZSBjb2hvcnQ6IGRvZXMgbW9kZWwgQSBjYXRjaCBjYXNlcyBCIG1pc3Nlcz8NCm1jbmVtYXJfc2VucyA8LSBmdW5jdGlvbihBLCBCKXsNCiAgeSAgPC0gcm9jX2xpc3RbW0FdXSRyZXNwb25zZQ0KICBwYSA8LSBpZmVsc2Uocm9jX2xpc3RbW0FdXSRwcmVkaWN0b3IgPj0gdGhyKEEpLCAxLCAwKQ0KICBwYiA8LSBpZmVsc2Uocm9jX2xpc3RbW0JdXSRwcmVkaWN0b3IgPj0gdGhyKEIpLCAxLCAwKQ0KICBpZHggPC0gd2hpY2goeSA9PSAxKQ0KICB0YWIgPC0gdGFibGUoQSA9IGZhY3RvcihwYVtpZHhdLCAwOjEpLCBCID0gZmFjdG9yKHBiW2lkeF0sIDA6MSkpDQogIHAgPC0gc3VwcHJlc3NXYXJuaW5ncyhtY25lbWFyLnRlc3QodGFiKSRwLnZhbHVlKQ0KICBjYXQoc3ByaW50ZigiTWNOZW1hciBzZW5zICAlLTMwcyB2cyAlLTI0cyA6IHAgPSAlLjRmXG4iLCBBLCBCLCBwKSkNCiAgZGF0YS5mcmFtZSh0ZXN0ID0gIk1jTmVtYXIoc2VucykiLCBBID0gQSwgQiA9IEIsIHN0YXQgPSBOQSwgcCA9IHJvdW5kKHAsIDQpKQ0KfQ0KDQojIERlTG9uZyBBVUMgY29tcGFyaXNvbi4NCmRlbG9uZyA8LSBmdW5jdGlvbihBLCBCKXsNCiAgdCA8LSByb2MudGVzdChyb2NfbGlzdFtbQV1dLCByb2NfbGlzdFtbQl1dLCBtZXRob2QgPSAiZGVsb25nIikNCiAgY2F0KHNwcmludGYoIkRlTG9uZyBBVUMgICAgJS0zMHMgdnMgJS0yNHMgOiBaID0gJS4zZiwgcCA9ICUuNGZcbiIsDQogICAgICAgICAgICAgIEEsIEIsIGFzLm51bWVyaWModCRzdGF0aXN0aWMpLCB0JHAudmFsdWUpKQ0KICBkYXRhLmZyYW1lKHRlc3QgPSAiRGVMb25nKEFVQykiLCBBID0gQSwgQiA9IEIsDQogICAgICAgICAgICAgc3RhdCA9IHJvdW5kKGFzLm51bWVyaWModCRzdGF0aXN0aWMpLCAzKSwgcCA9IHJvdW5kKHQkcC52YWx1ZSwgNCkpDQp9DQoNCk01IDwtICJNNTogQUxMIChNSUNFICsgQ29udCArIFNNT1RFKSINCnJlcyA8LSBsaXN0KCkNCg0KY2F0KCJcbj09PSBIMSAgdG9wb2xvZ3ksIG1pbm9yaXR5IHNlbnNpdGl2aXR5IChNY05lbWFyKSA9PT1cbiIpDQpyZXNbWzFdXSA8LSBtY25lbWFyX3NlbnMoIk0zOiBDb250aW51b3VzIE5vZGVzIiwgICJNMTogQmFzZWxpbmUiKQ0KcmVzW1syXV0gPC0gbWNuZW1hcl9zZW5zKCJNNjogTUlDRSArIENvbnRpbnVvdXMiLCAiTTI6IE1JQ0UgKyBEaXNjcmV0aXplIikNCg0KY2F0KCJcbj09PSBIMyAgU01PVEUsIG1pbm9yaXR5IHNlbnNpdGl2aXR5IChNY05lbWFyKSA9PT1cbiIpDQpyZXNbWzNdXSA8LSBtY25lbWFyX3NlbnMoIk00OiBTTU9URSBvbiBCYXNlbGluZSIsICJNMTogQmFzZWxpbmUiKQ0KcmVzW1s0XV0gPC0gbWNuZW1hcl9zZW5zKCJNODogTUlDRSArIFNNT1RFIiwgICAgICAiTTI6IE1JQ0UgKyBEaXNjcmV0aXplIikNCnJlc1tbNV1dIDwtIG1jbmVtYXJfc2VucyhNNSwgICAgICAgICAgICAgICAgICAgICAgICJNNjogTUlDRSArIENvbnRpbnVvdXMiKQ0KDQpjYXQoIlxuPT09IEg0ICBzeW5lcmd5IHZzIGFic29sdXRlIGJhc2VsaW5lIChEZUxvbmcgQVVDKSA9PT1cbiIpDQpyZXNbWzZdXSA8LSBkZWxvbmcoTTUsICAgICAgICAgICAgICAgICAgICAgICAiTTE6IEJhc2VsaW5lIikNCnJlc1tbN11dIDwtIGRlbG9uZygiTTY6IE1JQ0UgKyBDb250aW51b3VzIiwgICJNMTogQmFzZWxpbmUiKQ0KDQpwdmFsX3RhYmxlIDwtIGRvLmNhbGwocmJpbmQsIHJlcykNCnByaW50KHB2YWxfdGFibGUpDQp3cml0ZS5jc3YocHZhbF90YWJsZSwgImh5cG90aGVzaXNfcHZhbHVlcy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCmNhdCgiXG5TYXZlZCBoeXBvdGhlc2lzX3B2YWx1ZXMuY3N2XG4iKQ0KDQpgYGANCg0KDQojIDE0ICBSb2J1c3RuZXNzIOKAlCAzMCByZXBlYXRlZCBzdHJhdGlmaWVkIHNwbGl0cw0KYGBge3Igcm9idXN0bmVzc30NCiMgQSBzaW5nbGUgc3BsaXQgaXMgZnJhZ2lsZSBvbiAyNDkgZXZlbnRzLiBSZXBlYXQgdGhlIFdIT0xFIHBpcGVsaW5lIG92ZXIgbWFueQ0KIyBzZWVkczsgcmVwb3J0IG1lYW4gKy8tIFNEIEFVQywgYW5kIGEgUEFJUkVEIHRlc3Qgb2YgY29udGludW91cyB2cyBkaXNjcmV0ZSBvbg0KIyB0aGUgcGVyLXNlZWQgQVVDIGRpZmZlcmVuY2VzLiBUaGlzIGlzIGZhciBzdHJvbmdlciB0aGFuIG9uZSBEZUxvbmcgcC12YWx1ZS4NCiMgTk9URTogY29udGludW91cyBhcm1zIHVzZSBwZXItcGF0aWVudCBsaWtlbGlob29kIHdlaWdodGluZzsgbiBpcyBsb3dlcmVkIHRvIDUwMA0KIyBoZXJlIGZvciBydW50aW1lLiBSYWlzZSBOX1JFUEVBVFMgLyBuIGZvciB0aGUgZmluYWwgY2FtZXJhLXJlYWR5IHJ1bi4NCk5fUkVQRUFUUyA8LSAzMA0KDQpldmFsX2xpZ2h0IDwtIGZ1bmN0aW9uKHRyLCB2YSwgdGUsIGRpc2Mpew0KICB3bCA8LSBpZiAoZGlzYykgd2xfZGlzY3JldGUgZWxzZSB3bF9oeWJyaWQ7IGJsIDwtIGlmIChkaXNjKSBibF9kaXNjcmV0ZSBlbHNlIGJsX2h5YnJpZA0KICBkYWcgPC0gaGModHIsIHdoaXRlbGlzdD13bCwgYmxhY2tsaXN0PWJsKQ0KICBmaXQgPC0gaWYgKGRpc2MpIGJuLmZpdChkYWcsIHRyLCBtZXRob2Q9ImJheWVzIiwgaXNzPTEwKSBlbHNlIGJuLmZpdChkYWcsIHRyKQ0KICBncCA8LSBmdW5jdGlvbihkKXsNCiAgICBpZiAoZGlzYyl7IHBwIDwtIHByZWRpY3QoZml0LCBub2RlPSJzdHJva2UiLCBkYXRhPWQsIG1ldGhvZD0iYmF5ZXMtbHciLCBwcm9iPVRSVUUpDQogICAgICAgICAgICAgICByZXR1cm4oYXR0cihwcCwicHJvYiIpWyIxIixdKSB9DQogICAgZWMgPC0gc2V0ZGlmZihuYW1lcyhkKSwic3Ryb2tlIikNCiAgICB2YXBwbHkoc2VxX2xlbihucm93KGQpKSwgZnVuY3Rpb24oaSl7DQogICAgICBwIDwtIHN1cHByZXNzV2FybmluZ3MoY3BxdWVyeShmaXQsIGV2ZW50PShzdHJva2U9PSIxIiksDQogICAgICAgICAgICAgZXZpZGVuY2U9YXMubGlzdChkW2ksZWNdKSwgbWV0aG9kPSJsdyIsIG49NTAwKSk7IGlmIChpcy5uYShwKSkgMCBlbHNlIHB9LCBudW1lcmljKDEpKQ0KICB9DQogIGFzLm51bWVyaWMoYXVjKHJvYyh0ZSRzdHJva2UsIGdwKHRlKSwgbGV2ZWxzPWMoIjAiLCIxIiksIHF1aWV0PVRSVUUpKSkNCn0NCg0Kcm9iIDwtIGRhdGEuZnJhbWUoKQ0KZm9yIChzIGluIDE6Tl9SRVBFQVRTKSB7DQogIHNwIDwtIHNwbGl0X3N0cm9rZShzdHJva2VfZGF0YSwgc2VlZCA9IDEwMDAgKyBzKQ0KICBzcCR0cmFpbiRzcGxpdDwtTlVMTDsgIyBzYWZldHkNCiAgbSAgPC0gbWVkaWFuKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHNwJHRyYWluJGJtaSkpLCBuYS5ybT1UUlVFKQ0KICAjIE1JQ0UgKGxlYWthZ2Utc2FmZSkgZm9yIHRoaXMgc3BsaXQNCiAgY2IgPC0gYmluZF9yb3dzKG11dGF0ZShzcCR0cmFpbixzcGxpdD0idHJhaW4iKSwgbXV0YXRlKHNwJHZhbCxzcGxpdD0idmFsIiksDQogICAgICAgICAgICAgICAgICBtdXRhdGUoc3AkdGVzdCxzcGxpdD0idGVzdCIpKSAlPiUgc2VsZWN0KC1hbnlfb2YoImlkIikpDQogIG1tIDwtIHN1cHByZXNzV2FybmluZ3MobWljZShjYiwgbT0xLCBpZ25vcmU9Y2Ikc3BsaXQhPSJ0cmFpbiIsIG1heGl0PTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkPTEwMDArcywgcHJpbnRGbGFnPUZBTFNFKSkNCiAgZG4gPC0gY29tcGxldGUobW0sMSkNCiAgdHIyPC1kbiU+JWZpbHRlcihzcGxpdD09InRyYWluIiklPiVzZWxlY3QoLXNwbGl0KQ0KICB0ZTI8LWRuJT4lZmlsdGVyKHNwbGl0PT0idGVzdCIpICU+JXNlbGVjdCgtc3BsaXQpDQogIHNhZmUgPC0gZnVuY3Rpb24oZXhwcikgdHJ5Q2F0Y2goZXhwciwgZXJyb3I9ZnVuY3Rpb24oZSl7IGNhdCgiICAoc2tpcHBlZDoiLGUkbWVzc2FnZSwiKVxuIik7IE5BX3JlYWxfIH0pDQogIHJvYiA8LSByYmluZChyb2IsIGRhdGEuZnJhbWUoc2VlZD1zLA0KICAgIE0xPXNhZmUoZXZhbF9saWdodChpbXB1dGVfYmFzZWxpbmUoc3AkdHJhaW4sbSksIE5VTEwsIGltcHV0ZV9iYXNlbGluZShzcCR0ZXN0LG0pLCBUUlVFKSksDQogICAgTTI9c2FmZShldmFsX2xpZ2h0KGltcHV0ZV9iYXNlbGluZSh0cjIsbSksICAgICAgTlVMTCwgaW1wdXRlX2Jhc2VsaW5lKHRlMixtKSwgICAgICBUUlVFKSksDQogICAgTTM9c2FmZShldmFsX2xpZ2h0KHByZXBfY29udGludW91cyhzcCR0cmFpbixtKSwgTlVMTCwgcHJlcF9jb250aW51b3VzKHNwJHRlc3QsbSksICBGQUxTRSkpLA0KICAgIE02PXNhZmUoZXZhbF9saWdodChwcmVwX2NvbnRpbnVvdXModHIyLG0pLCAgICAgIE5VTEwsIHByZXBfY29udGludW91cyh0ZTIsbSksICAgICAgIEZBTFNFKSkpKQ0KICBjYXQoc3ByaW50Zigicm9idXN0IHNlZWQgJWQvJWQgZG9uZVxuIiwgcywgTl9SRVBFQVRTKSkNCn0NCg0Kcm9iX3N1bW1hcnkgPC0gZGF0YS5mcmFtZSgNCiAgQXJtID0gYygiTTE6IEJhc2VsaW5lIiwiTTI6IE1JQ0UrRGlzYyIsIk0zOiBDb250aW51b3VzIiwiTTY6IE1JQ0UrQ29udGludW91cyIpLA0KICBBVUNfbWVhbiA9IHJvdW5kKGMobWVhbihyb2IkTTEsbmEucm09VFJVRSksIG1lYW4ocm9iJE0yLG5hLnJtPVRSVUUpLCBtZWFuKHJvYiRNMyxuYS5ybT1UUlVFKSwgbWVhbihyb2IkTTYsbmEucm09VFJVRSkpLDQpLA0KICBBVUNfc2QgICA9IHJvdW5kKGMoc2Qocm9iJE0xLG5hLnJtPVRSVUUpLCAgIHNkKHJvYiRNMixuYS5ybT1UUlVFKSwgICBzZChyb2IkTTMsbmEucm09VFJVRSksICAgc2Qocm9iJE02LG5hLnJtPVRSVUUpKSw0KSkNCnByaW50KHJvYl9zdW1tYXJ5KTsgd3JpdGUuY3N2KHJvYl9zdW1tYXJ5LCAicm9idXN0bmVzc19zdW1tYXJ5LmNzdiIsIHJvdy5uYW1lcz1GQUxTRSkNCg0KY2F0KCJcblBhaXJlZCB0ZXN0cyAoY29udGludW91cyB2cyBkaXNjcmV0ZSkgYWNyb3NzIiwgTl9SRVBFQVRTLCAic2VlZHM6XG4iKQ0KY2F0KCJIMSBwcmltYXJ5ICBNMyB2cyBNMTpcbiIpOyBwcmludCh0LnRlc3Qocm9iJE0zLCByb2IkTTEsIHBhaXJlZD1UUlVFKSk7IHByaW50KHdpbGNveC50ZXN0KHJvYiRNMywgcm9iJE0xLCBwYWlyZWQ9VFJVRSkpDQpjYXQoIkgxIHN1cHBvcnQgIE02IHZzIE0yOlxuIik7IHByaW50KHQudGVzdChyb2IkTTYsIHJvYiRNMiwgcGFpcmVkPVRSVUUpKTsgcHJpbnQod2lsY294LnRlc3Qocm9iJE02LCByb2IkTTIsIHBhaXJlZD1UUlVFKSkNCndyaXRlLmNzdihyb2IsICJyb2J1c3RuZXNzX3Jhd19hdWMuY3N2Iiwgcm93Lm5hbWVzPUZBTFNFKQ0KYGBgDQoNCiMgMTUgIENsaW5pY2FsIGluZmVyZW5jZSAmIHdoYXQtaWYgc2NlbmFyaW9zDQpgYGB7ciBpbmZlcmVuY2V9DQpmaXR0ZWRfbmV0IDwtIGZpdF9saXN0W1siTTY6IE1JQ0UgKyBDb250aW51b3VzIl1dDQpzZXQuc2VlZCgyMDI2KTsgbl9zaW0gPC0gMWU2ICAgIyBsb2dpYyBzYW1wbGluZzsgc2VlZCBmaXhlcyB0aGUgcmVwb3J0ZWQgdmFsdWVzDQoNCiMgRXZpZGVuY2UgbXVzdCBiZSB3cml0dGVuIElOTElORTogY3BxdWVyeSB1c2VzIG5vbi1zdGFuZGFyZCBldmFsdWF0aW9uIGFuZCB3aWxsDQojIG5vdCBhY2NlcHQgYSBxdW90ZWQgZXhwcmVzc2lvbiBwYXNzZWQgdGhyb3VnaCBhIHZhcmlhYmxlLg0KcEEgPC0gY3BxdWVyeShmaXR0ZWRfbmV0LCBldmVudD0oc3Ryb2tlPT0iMSIpLA0KICAgICAgICAgICAgICBldmlkZW5jZT0oYWdlPD01MCAmIGh5cGVydGVuc2lvbj09IjAiICYgaGVhcnRfZGlzZWFzZT09IjAiKSwgbj1uX3NpbSkNCnBCIDwtIGNwcXVlcnkoZml0dGVkX25ldCwgZXZlbnQ9KHN0cm9rZT09IjEiKSwNCiAgICAgICAgICAgICAgZXZpZGVuY2U9KGFnZT49NjUgJiBoeXBlcnRlbnNpb249PSIxIiAmIGhlYXJ0X2Rpc2Vhc2U9PSIxIiAmIGF2Z19nbHVjb3NlX2xldmVsPj0xNTApLCBuPW5fc2ltKQ0KcEMgPC0gY3BxdWVyeShmaXR0ZWRfbmV0LCBldmVudD0oc3Ryb2tlPT0iMSIpLA0KICAgICAgICAgICAgICBldmlkZW5jZT0oYWdlPj02NSAmIGh5cGVydGVuc2lvbj09IjEiICYgaGVhcnRfZGlzZWFzZT09IjEiICYgYXZnX2dsdWNvc2VfbGV2ZWw8PTEwMCksIG49bl9zaW0pDQpjYXQoc3ByaW50ZigiUGVyc29uYSBBIChsb3cgcmlzayk6ICAgICAgICAgICAlLjJmJSVcbiIsIDEwMCpwQSkpDQpjYXQoc3ByaW50ZigiUGVyc29uYSBCIChoaWdoIHJpc2spOiAgICAgICAgICAlLjJmJSVcbiIsIDEwMCpwQikpDQpjYXQoc3ByaW50ZigiUGVyc29uYSBDIChCICsgZ2x1Y29zZSBtYW5hZ2VkKTogJS4yZiUlXG4iLCAxMDAqcEMpKQ0KY2F0KHNwcmludGYoIkFic29sdXRlIHJpc2sgcmVkdWN0aW9uOiAgICAgICAgJS4yZiUlIC0+ICUuMmYlJVxuIiwgMTAwKnBCLCAxMDAqcEMpKQ0KYGBgDQoNCiMgMTYgIFNlc3Npb24gaW5mbyAgKHJlcHJvZHVjaWJpbGl0eSByZWNvcmQpDQpgYGB7ciBzZXNzaW9ufQ0Kd3JpdGVMaW5lcyhjYXB0dXJlLm91dHB1dChzZXNzaW9uSW5mbygpKSwgInNlc3Npb25JbmZvLnR4dCIpDQpzZXNzaW9uSW5mbygpDQpgYGANCg==