Helper functions
rules<-function(data,formula_string){
data<-analysis(data)
tree<-rpart::rpart(formula(formula_string),data=data, control = rpart.control(minsplit = 10))
rpart.rules(tree)
}
nrules<-function(data,formula_string){
data<-analysis(data)
tree<-rpart::rpart(formula(formula_string),data=data, control = rpart.control(minsplit = 10))
#tree<-prune(tree,cp=0.010)
rpart.rules(tree) %>% nrow()
#preds<-predict(tree,retropostion_data,type = 'class')
#caret::confusionMatrix(preds,as.factor(retropostion_data$N_POSITION))
}
plot_ranges <- function(resamples,formula_s) {
resamples$trees <- map(.x=resamples$splits,formula_string=formula_s, rules)
resamples$trees <-
map(resamples$trees, function(x)
if (x %>% ncol() == 8)
x)
resamples$trees <-
map(resamples$trees, function(x)
if (!is.null(x)) {
names(x) <- 1:8
x
})
fullset_trees <- do.call(rbind, resamples$trees)
fullset_trees <- fullset_trees %>% as.data.frame()
ggplot(fullset_trees) +
facet_wrap( ~ `1` + `5`,ncol = 2) +
geom_boxplot(aes(x = 1, y = fullset_trees[['8']] %>% as.double()), color =
'red') +
geom_boxplot(aes(x = 2, y = fullset_trees[['6']] %>% as.double()), color =
'blue') +
#geom_line(aes(x = fullset_trees[['8']] %>% as.double()), stat = "density", adjust = 1.25,color='red') +
#geom_line(aes(x = fullset_trees[['6']] %>% as.double()), stat = "density", adjust = 1.25,color='blue') +
xlab("CART: POSITION ranges: Upper limit in red, Lower limit in blue.") +
ylab("values") +
theme_bw()
}
plot_nrules <- function(resamples,formula_s) {
nrules <- map(.x=resamples$splits,formula_string=formula_s, nrules)
fullset_nrules <- do.call(rbind, nrules)
ggplot(fullset_nrules %>% as.data.frame(), aes(x = V1)) +
geom_line(stat = "density", adjust = 1.25) +
#geom_boxplot(x = as.integer(1), color = 'red') +
xlab("CART: number of rules for POSITION") +
#ylab("rules")+
theme_bw()
}
formula_s="T_POSITION~NORM_T_RETROPOSITION"
plot_rules_overlaping<-function(resamples,formula_s){
resamples$trees <- map(.x=resamples$splits,formula_string=formula_s , rules)
resamples$trees <-
map(resamples$trees, function(x)
if (x %>% ncol() == 8)
x)
resamples$trees <-
map(resamples$trees, function(x)
if (!is.null(x)) {
names(x) <- 1:8
x
})
fullset_trees <- do.call(rbind, resamples$trees)
fullset_trees <- fullset_trees %>% as.data.frame()
fullset_trees_ranges<-fullset_trees %>% as.data.frame() %>% select(c(1,5,6,8))
names(fullset_trees_ranges)<-c("POSITION","condition","lower_limit","upper_limit")
fullset_trees_ranges[['lower_limit']]<- fullset_trees_ranges[['lower_limit']] %>% as.double()
fullset_trees_ranges[['upper_limit']]<- fullset_trees_ranges[['upper_limit']] %>% as.double()
fullset_trees_ranges %>% mutate(ll=ifelse(condition==">=",upper_limit,lower_limit))%>% mutate(ul=ifelse(is.na(lower_limit),1,upper_limit)) %>%
mutate(ul=ifelse(condition=="< ",lower_limit,ul)) %>%
mutate(ll=ifelse(is.na(upper_limit),0,ll)) %>% select(POSITION,ll,ul) %>%
ggplot()+
#geom_line(aes(x=0:0.34,y=POSITION,color=POSITION),size=5)
geom_segment(aes(y=POSITION %>% factor,
yend=POSITION %>% factor,
x=ul,
xend=ll,
color=POSITION %>% factor),
size=5,alpha=0.01
)+
xlim(0,1)+
ylab("POSITION")+xlab("Normalized Range")+
theme_bw()+theme(legend.position = "none")
}
NASAL POSITION
CART model
tree <-
rpart::rpart(
N_POSITION ~ NORM_N_RETROPOSITION,
data = retropostion_data,
control = rpart.control(minsplit = 10, xval = 10)
)
rpart.plot(
tree,
type = 1,
extra = 101,
box.palette = "GnBu",
branch.lty = 3,
shadow.col = "gray",
nn = TRUE
)

printcp(tree)
Classification tree:
rpart::rpart(formula = N_POSITION ~ NORM_N_RETROPOSITION, data = retropostion_data,
control = rpart.control(minsplit = 10, xval = 10))
Variables actually used in tree construction:
[1] NORM_N_RETROPOSITION
Root node error: 16/63 = 0.25397
n= 63
CP nsplit rel error xerror xstd
1 0.375 0 1.000 1.000 0.21593
2 0.125 1 0.625 0.625 0.18128
3 0.010 2 0.500 0.625 0.18128
rpart.rules(tree)
preds <- predict(tree, retropostion_data, type = 'class')
cm <-
caret::confusionMatrix(preds, as.factor(retropostion_data$N_POSITION))
cm
Confusion Matrix and Statistics
Reference
Prediction CA CP S
CA 4 0 2
CP 6 47 0
S 0 0 4
Overall Statistics
Accuracy : 0.873
95% CI : (0.765, 0.9435)
No Information Rate : 0.746
P-Value [Acc > NIR] : 0.01097
Kappa : 0.6385
Mcnemar's Test P-Value : NA
Statistics by Class:
Class: CA Class: CP Class: S
Sensitivity 0.40000 1.0000 0.66667
Specificity 0.96226 0.6250 1.00000
Pos Pred Value 0.66667 0.8868 1.00000
Neg Pred Value 0.89474 1.0000 0.96610
Prevalence 0.15873 0.7460 0.09524
Detection Rate 0.06349 0.7460 0.06349
Detection Prevalence 0.09524 0.8413 0.06349
Balanced Accuracy 0.68113 0.8125 0.83333
Calculating confidence intervals using one-tailed binomial test
The binomial test is an exact test of the statistical significance of deviations from a theoretically expected distribution of observations into two categories. for this case we consider each category/class against the rest. The clasical one-vs-all approach for dealing with multiclasses situations
Sulcus
binom.test(cm$table[3,3] ,cm$table[1:3,3] %>% sum())
Exact binomial test
data: cm$table[3, 3] and cm$table[1:3, 3] %>% sum()
number of successes = 4, number of trials = 6, p-value = 0.6875
alternative hypothesis: true probability of success is not equal to 0.5
95 percent confidence interval:
0.2227781 0.9567281
sample estimates:
probability of success
0.6666667
Ciliary Body Posterior
binom.test(cm$table[2,2] ,cm$table[1:3,2] %>% sum())
Exact binomial test
data: cm$table[2, 2] and cm$table[1:3, 2] %>% sum()
number of successes = 47, number of trials = 47, p-value = 1.421e-14
alternative hypothesis: true probability of success is not equal to 0.5
95 percent confidence interval:
0.9245143 1.0000000
sample estimates:
probability of success
1
Ciliary Body Anterior
binom.test(cm$table[1,1] ,cm$table[1:3,1] %>% sum())
Exact binomial test
data: cm$table[1, 1] and cm$table[1:3, 1] %>% sum()
number of successes = 4, number of trials = 10, p-value = 0.7539
alternative hypothesis: true probability of success is not equal to 0.5
95 percent confidence interval:
0.1215523 0.7376219
sample estimates:
probability of success
0.4
Analysis of rules found by CART
LOOCV
Similar to CV with K = N, where N is the size of the dataset. Ideally, this approach has a low variance in the results.
Number of rules per model distribution
resamples<-loo_cv(retropostion_data)
plot_nrules(resamples,"N_POSITION~NORM_N_RETROPOSITION")

Range variation for models with rulset size = 3
Rules can have an upper limit (>=), lower limit(<) or both (is)
plot_ranges(resamples,"N_POSITION~NORM_N_RETROPOSITION")

Range Overlaping for ruleset size = 3
plot_rules_overlaping(resamples,"N_POSITION~NORM_N_RETROPOSITION")

BOOTSTRAP
We generate 2000 new samples with replacement and we build 2000 new CARTs.
Number of rules per model distribution
resamples<-bootstraps(retropostion_data,strata = N_POSITION,times = 2000 )
plot_nrules(resamples,"N_POSITION~NORM_N_RETROPOSITION")

Range variation for models with rulset size = 3
Rules can have an upper limit (>), lower limit(<) or both (is)
plot_ranges(resamples,"N_POSITION~NORM_N_RETROPOSITION")

Range Overlaping for ruleset size = 3
plot_rules_overlaping(resamples,"N_POSITION~NORM_N_RETROPOSITION")

TEMPORAL POSITION
CART model
tree <-
rpart::rpart(
T_POSITION ~ NORM_T_RETROPOSITION,
data = retropostion_data,
control = rpart.control(minsplit = 10, xval = 10)
)
rpart.plot(
tree,
type = 1,
extra = 101,
box.palette = "GnBu",
branch.lty = 3,
shadow.col = "gray",
nn = TRUE
)

printcp(tree)
Classification tree:
rpart::rpart(formula = T_POSITION ~ NORM_T_RETROPOSITION, data = retropostion_data,
control = rpart.control(minsplit = 10, xval = 10))
Variables actually used in tree construction:
[1] NORM_T_RETROPOSITION
Root node error: 14/63 = 0.22222
n= 63
CP nsplit rel error xerror xstd
1 0.500000 0 1.00000 1.00000 0.23570
2 0.035714 1 0.50000 0.57143 0.18877
3 0.010000 3 0.42857 0.57143 0.18877
rpart.rules(tree)
preds <- predict(tree, retropostion_data, type = 'class')
cm <-
caret::confusionMatrix(preds, as.factor(retropostion_data$T_POSITION))
cm
Confusion Matrix and Statistics
Reference
Prediction CA CP S
CA 2 1 0
CP 2 48 1
S 2 0 7
Overall Statistics
Accuracy : 0.9048
95% CI : (0.8041, 0.9642)
No Information Rate : 0.7778
P-Value [Acc > NIR] : 0.007371
Kappa : 0.7261
Mcnemar's Test P-Value : 0.343030
Statistics by Class:
Class: CA Class: CP Class: S
Sensitivity 0.33333 0.9796 0.8750
Specificity 0.98246 0.7857 0.9636
Pos Pred Value 0.66667 0.9412 0.7778
Neg Pred Value 0.93333 0.9167 0.9815
Prevalence 0.09524 0.7778 0.1270
Detection Rate 0.03175 0.7619 0.1111
Detection Prevalence 0.04762 0.8095 0.1429
Balanced Accuracy 0.65789 0.8827 0.9193
Calculating confidence intervals using one-tailed binomial test
Sulcus
binom.test(cm$table[3,3] ,cm$table[1:3,3] %>% sum())
Exact binomial test
data: cm$table[3, 3] and cm$table[1:3, 3] %>% sum()
number of successes = 7, number of trials = 8, p-value = 0.07031
alternative hypothesis: true probability of success is not equal to 0.5
95 percent confidence interval:
0.4734903 0.9968403
sample estimates:
probability of success
0.875
Ciliary Body Posterior
binom.test(cm$table[2,2] ,cm$table[1:3,2] %>% sum())
Exact binomial test
data: cm$table[2, 2] and cm$table[1:3, 2] %>% sum()
number of successes = 48, number of trials = 49, p-value = 1.776e-13
alternative hypothesis: true probability of success is not equal to 0.5
95 percent confidence interval:
0.8914582 0.9994834
sample estimates:
probability of success
0.9795918
Ciliary Body Anterior
binom.test(cm$table[1,1] ,cm$table[1:3,1] %>% sum())
Exact binomial test
data: cm$table[1, 1] and cm$table[1:3, 1] %>% sum()
number of successes = 2, number of trials = 6, p-value = 0.6875
alternative hypothesis: true probability of success is not equal to 0.5
95 percent confidence interval:
0.04327187 0.77722190
sample estimates:
probability of success
0.3333333
Analysis of rules found by CART
LOOCV
Similar to CV with K = N, where N is the size of the dataset. Ideally, this approach has a low variance in the results.
Number of rules per model distribution
resamples<-loo_cv(retropostion_data)
plot_nrules(resamples,"T_POSITION~NORM_T_RETROPOSITION")

Range variation for models with rulset
Rules can have an upper limit (>=), lower limit(<) or both (is) (*) Ruleset should include at least one rule with upper and lower limit
plot_ranges(resamples,"T_POSITION~NORM_T_RETROPOSITION")

Range Overlaping for ruleset
plot_rules_overlaping(resamples,"T_POSITION~NORM_T_RETROPOSITION")

BOOTSTRAP
We generate 2000 new samples with replacement and we build 2000 new CARTs.
Number of rules per model distribution
resamples<-bootstraps(retropostion_data,strata = N_POSITION,times = 2000 )
plot_nrules(resamples,"T_POSITION~NORM_T_RETROPOSITION")

Range variation for models with ruleset
Rules can have an upper limit (>), lower limit(<) or both (is) (*) Ruleset should include at least one rule with upper and lower limit
plot_ranges(resamples,"T_POSITION~NORM_T_RETROPOSITION")

Range Overlaping for ruleset
plot_rules_overlaping(resamples,"T_POSITION~NORM_T_RETROPOSITION")

LS0tCnRpdGxlOiAiSVogSUNMIFBPU0lUSU9OIEFOQUxZU0lTIChSRVNBTVBMSU5HKSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19jb2xsYXBzZWQ6IHRydWUKLS0tCgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShza2ltcikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShWSU0pCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShjYXJldCkKbGlicmFyeShyc2FtcGxlKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGdnc3RhbmNlKQpgYGAKCiMjIFJlYWRpbmcgYW5kIHByZXByb2Nlc2luZyBkYXRhdGFzZXQKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnBvc3RvcF9kYXRhIDwtCiAgcmVhZHI6OnJlYWRfY3N2KGZpbGUgPSAicG9zdG9wX2RhdGFfY2xlYW5lZF9ub3JtYWxpemVkLXZlcnNpb241LXJldGVzdC5jc3YiKQpyZXRyb3Bvc3Rpb25fZGF0YSA8LSBwb3N0b3BfZGF0YSAlPiUgc2VsZWN0KAogIE5BTUUsCiAgTk9STV9OX1JFVFJPUE9TSVRJT04sCiAgTk9STV9UX1JFVFJPUE9TSVRJT04sCiAgUE9TSVRJT04sCiAgTl9SRVRST1BPU0lUSU9OX01NLAogIFRfUkVUUk9QT1NJVElPTl9NTSwKICBSRVRFU1RfUE9TSVRJT04sCiAgTk9STV9UX1JFVFJPX0hBTEZfSU9MLAogIE5PUk1fTl9SRVRST19IQUxGX0lPTCwKICBOT1JNX1RfUkVUUk9fSU9MLAogIE5PUk1fTl9SRVRST19JT0wKICAKICAKKSAlPiUgZmlsdGVyKCFpcy5uYShSRVRFU1RfUE9TSVRJT04pKSAlPiUgc2VwYXJhdGUoUkVURVNUX1BPU0lUSU9OLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRvID0gYygiTl9QT1NJVElPTiIsICJUX1BPU0lUSU9OIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICctJykgJT4lCiAgZHJvcF9uYShUX1BPU0lUSU9OKSAlPiUgZHJvcF9uYShOT1JNX1RfUkVUUk9QT1NJVElPTikKCnJldHJvcG9zdGlvbl9kYXRhIDwtCiAgcmV0cm9wb3N0aW9uX2RhdGEgJT4lIG11dGF0ZSgKICAgIFRfUE9TSVRJT04gPSBpZmVsc2UoVF9QT1NJVElPTiA9PSAiQ00iLCAiQ0EiLCBUX1BPU0lUSU9OKSwKICAgIE5fUE9TSVRJT04gPSBpZmVsc2UoTl9QT1NJVElPTiA9PSAiQ00iLCAiQ0EiLCBOX1BPU0lUSU9OKQogICkKcmV0cm9wb3N0aW9uX2RhdGEgJT4lCiAgZ3JvdXBfYnkoTl9QT1NJVElPTikgJT4lCiAgc3VtbWFyaXNlKHRvdGFsID0gbigpKQoKcmV0cm9wb3N0aW9uX2RhdGEKYGBgCgojIyBIZWxwZXIgZnVuY3Rpb25zCmBgYHtyfQpydWxlczwtZnVuY3Rpb24oZGF0YSxmb3JtdWxhX3N0cmluZyl7CiAgZGF0YTwtYW5hbHlzaXMoZGF0YSkKICB0cmVlPC1ycGFydDo6cnBhcnQoZm9ybXVsYShmb3JtdWxhX3N0cmluZyksZGF0YT1kYXRhLCBjb250cm9sID0gIHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAxMCkpCiAgcnBhcnQucnVsZXModHJlZSkgCn0KbnJ1bGVzPC1mdW5jdGlvbihkYXRhLGZvcm11bGFfc3RyaW5nKXsKICBkYXRhPC1hbmFseXNpcyhkYXRhKQogIHRyZWU8LXJwYXJ0OjpycGFydChmb3JtdWxhKGZvcm11bGFfc3RyaW5nKSxkYXRhPWRhdGEsIGNvbnRyb2wgPSAgcnBhcnQuY29udHJvbChtaW5zcGxpdCA9IDEwKSkKICAjdHJlZTwtcHJ1bmUodHJlZSxjcD0wLjAxMCkKICBycGFydC5ydWxlcyh0cmVlKSAlPiUgbnJvdygpCiAgI3ByZWRzPC1wcmVkaWN0KHRyZWUscmV0cm9wb3N0aW9uX2RhdGEsdHlwZSA9ICdjbGFzcycpCiAgI2NhcmV0Ojpjb25mdXNpb25NYXRyaXgocHJlZHMsYXMuZmFjdG9yKHJldHJvcG9zdGlvbl9kYXRhJE5fUE9TSVRJT04pKQp9CgpwbG90X3JhbmdlcyA8LSBmdW5jdGlvbihyZXNhbXBsZXMsZm9ybXVsYV9zKSB7CiAgcmVzYW1wbGVzJHRyZWVzIDwtIG1hcCgueD1yZXNhbXBsZXMkc3BsaXRzLGZvcm11bGFfc3RyaW5nPWZvcm11bGFfcywgcnVsZXMpCiAgcmVzYW1wbGVzJHRyZWVzIDwtCiAgICBtYXAocmVzYW1wbGVzJHRyZWVzLCBmdW5jdGlvbih4KQogICAgICBpZiAoeCAlPiUgbmNvbCgpID09IDgpCiAgICAgICAgeCkKICByZXNhbXBsZXMkdHJlZXMgPC0KICAgIG1hcChyZXNhbXBsZXMkdHJlZXMsIGZ1bmN0aW9uKHgpCiAgICAgIGlmICghaXMubnVsbCh4KSkgewogICAgICAgIG5hbWVzKHgpIDwtIDE6OAogICAgICAgIHgKICAgICAgfSkKICBmdWxsc2V0X3RyZWVzIDwtIGRvLmNhbGwocmJpbmQsIHJlc2FtcGxlcyR0cmVlcykKICBmdWxsc2V0X3RyZWVzIDwtIGZ1bGxzZXRfdHJlZXMgJT4lIGFzLmRhdGEuZnJhbWUoKQogIGdncGxvdChmdWxsc2V0X3RyZWVzKSArCiAgICBmYWNldF93cmFwKCB+IGAxYCArIGA1YCxuY29sID0gMikgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gMSwgeSA9IGZ1bGxzZXRfdHJlZXNbWyc4J11dICAlPiUgYXMuZG91YmxlKCkpLCBjb2xvciA9CiAgICAgICAgICAgICAgICAgICAncmVkJykgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gMiwgeSA9IGZ1bGxzZXRfdHJlZXNbWyc2J11dICAlPiUgYXMuZG91YmxlKCkpLCBjb2xvciA9CiAgICAgICAgICAgICAgICAgICAnYmx1ZScpICsKICAgICNnZW9tX2xpbmUoYWVzKHggPSBmdWxsc2V0X3RyZWVzW1snOCddXSAgJT4lIGFzLmRvdWJsZSgpKSwgc3RhdCA9ICJkZW5zaXR5IiwgYWRqdXN0ID0gMS4yNSxjb2xvcj0ncmVkJykgKwogICAgI2dlb21fbGluZShhZXMoeCA9IGZ1bGxzZXRfdHJlZXNbWyc2J11dICAlPiUgYXMuZG91YmxlKCkpLCBzdGF0ID0gImRlbnNpdHkiLCBhZGp1c3QgPSAxLjI1LGNvbG9yPSdibHVlJykgKwogICAgeGxhYigiQ0FSVDogIFBPU0lUSU9OIHJhbmdlczogVXBwZXIgbGltaXQgaW4gcmVkLCBMb3dlciBsaW1pdCBpbiBibHVlLiIpICsKICAgIHlsYWIoInZhbHVlcyIpICsKICAgIHRoZW1lX2J3KCkKfQoKCnBsb3RfbnJ1bGVzIDwtIGZ1bmN0aW9uKHJlc2FtcGxlcyxmb3JtdWxhX3MpIHsKICBucnVsZXMgPC0gbWFwKC54PXJlc2FtcGxlcyRzcGxpdHMsZm9ybXVsYV9zdHJpbmc9Zm9ybXVsYV9zLCBucnVsZXMpCiAgZnVsbHNldF9ucnVsZXMgPC0gZG8uY2FsbChyYmluZCwgbnJ1bGVzKQogIGdncGxvdChmdWxsc2V0X25ydWxlcyAlPiUgYXMuZGF0YS5mcmFtZSgpLCBhZXMoeCA9IFYxKSkgKwogICAgZ2VvbV9saW5lKHN0YXQgPSAiZGVuc2l0eSIsIGFkanVzdCA9IDEuMjUpICsKICAgICNnZW9tX2JveHBsb3QoeCA9IGFzLmludGVnZXIoMSksIGNvbG9yID0gJ3JlZCcpICsKICAgIHhsYWIoIkNBUlQ6IG51bWJlciBvZiBydWxlcyBmb3IgUE9TSVRJT04iKSArCiAgICAjeWxhYigicnVsZXMiKSsKICAgIHRoZW1lX2J3KCkKfQoKZm9ybXVsYV9zPSJUX1BPU0lUSU9Ofk5PUk1fVF9SRVRST1BPU0lUSU9OIgoKcGxvdF9ydWxlc19vdmVybGFwaW5nPC1mdW5jdGlvbihyZXNhbXBsZXMsZm9ybXVsYV9zKXsKIHJlc2FtcGxlcyR0cmVlcyA8LSBtYXAoLng9cmVzYW1wbGVzJHNwbGl0cyxmb3JtdWxhX3N0cmluZz1mb3JtdWxhX3MgLCBydWxlcykKICByZXNhbXBsZXMkdHJlZXMgPC0KICAgIG1hcChyZXNhbXBsZXMkdHJlZXMsIGZ1bmN0aW9uKHgpCiAgICAgIGlmICh4ICU+JSBuY29sKCkgPT0gOCkKICAgICAgICB4KQogIHJlc2FtcGxlcyR0cmVlcyA8LQogICAgbWFwKHJlc2FtcGxlcyR0cmVlcywgZnVuY3Rpb24oeCkKICAgICAgaWYgKCFpcy5udWxsKHgpKSB7CiAgICAgICAgbmFtZXMoeCkgPC0gMTo4CiAgICAgICAgeAogICAgICB9KQogIGZ1bGxzZXRfdHJlZXMgPC0gZG8uY2FsbChyYmluZCwgcmVzYW1wbGVzJHRyZWVzKQogIGZ1bGxzZXRfdHJlZXMgPC0gZnVsbHNldF90cmVlcyAlPiUgYXMuZGF0YS5mcmFtZSgpCiAgIAogIAogIApmdWxsc2V0X3RyZWVzX3JhbmdlczwtZnVsbHNldF90cmVlcyAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBzZWxlY3QoYygxLDUsNiw4KSkKbmFtZXMoZnVsbHNldF90cmVlc19yYW5nZXMpPC1jKCJQT1NJVElPTiIsImNvbmRpdGlvbiIsImxvd2VyX2xpbWl0IiwidXBwZXJfbGltaXQiKQpmdWxsc2V0X3RyZWVzX3Jhbmdlc1tbJ2xvd2VyX2xpbWl0J11dPC0gZnVsbHNldF90cmVlc19yYW5nZXNbWydsb3dlcl9saW1pdCddXSAlPiUgYXMuZG91YmxlKCkKZnVsbHNldF90cmVlc19yYW5nZXNbWyd1cHBlcl9saW1pdCddXTwtIGZ1bGxzZXRfdHJlZXNfcmFuZ2VzW1sndXBwZXJfbGltaXQnXV0gJT4lIGFzLmRvdWJsZSgpCgoKZnVsbHNldF90cmVlc19yYW5nZXMgJT4lICBtdXRhdGUobGw9aWZlbHNlKGNvbmRpdGlvbj09Ij49Iix1cHBlcl9saW1pdCxsb3dlcl9saW1pdCkpJT4lIG11dGF0ZSh1bD1pZmVsc2UoaXMubmEobG93ZXJfbGltaXQpLDEsdXBwZXJfbGltaXQpKSAlPiUKbXV0YXRlKHVsPWlmZWxzZShjb25kaXRpb249PSI8ICIsbG93ZXJfbGltaXQsdWwpKSAlPiUKbXV0YXRlKGxsPWlmZWxzZShpcy5uYSh1cHBlcl9saW1pdCksMCxsbCkpICAlPiUgc2VsZWN0KFBPU0lUSU9OLGxsLHVsKSAlPiUKICBnZ3Bsb3QoKSsKICAjZ2VvbV9saW5lKGFlcyh4PTA6MC4zNCx5PVBPU0lUSU9OLGNvbG9yPVBPU0lUSU9OKSxzaXplPTUpCiAgZ2VvbV9zZWdtZW50KGFlcyh5PVBPU0lUSU9OICU+JSBmYWN0b3IsCiAgICAgICAgICAgICAgICAgICB5ZW5kPVBPU0lUSU9OICU+JSBmYWN0b3IsCiAgICAgICAgICAgICAgICAgICB4PXVsLAogICAgICAgICAgICAgICAgICAgeGVuZD1sbCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPVBPU0lUSU9OICU+JSBmYWN0b3IpLAogICAgICAgICAgICAgICAgIHNpemU9NSxhbHBoYT0wLjAxCiAgICAgICAgICAgICAgICAgKSsKICB4bGltKDAsMSkrCiAgeWxhYigiUE9TSVRJT04iKSt4bGFiKCJOb3JtYWxpemVkIFJhbmdlIikrCiAgdGhlbWVfYncoKSt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIAp9CmBgYAoKCiMgTkFTQUwgUE9TSVRJT04KCiMjIyBDQVJUIG1vZGVsCgpgYGB7cn0KdHJlZSA8LQogIHJwYXJ0OjpycGFydCgKICAgIE5fUE9TSVRJT04gfiBOT1JNX05fUkVUUk9QT1NJVElPTiwKICAgIGRhdGEgPSByZXRyb3Bvc3Rpb25fZGF0YSwKICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMTAsIHh2YWwgPSAxMCkKICApCnJwYXJ0LnBsb3QoCiAgdHJlZSwKICB0eXBlID0gMSwKICBleHRyYSA9IDEwMSwKICBib3gucGFsZXR0ZSA9ICJHbkJ1IiwKICBicmFuY2gubHR5ID0gMywKICBzaGFkb3cuY29sID0gImdyYXkiLAogIG5uID0gVFJVRQopCnByaW50Y3AodHJlZSkKcnBhcnQucnVsZXModHJlZSkKCgpwcmVkcyA8LSBwcmVkaWN0KHRyZWUsIHJldHJvcG9zdGlvbl9kYXRhLCB0eXBlID0gJ2NsYXNzJykKY20gPC0KICBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHByZWRzLCBhcy5mYWN0b3IocmV0cm9wb3N0aW9uX2RhdGEkTl9QT1NJVElPTikpCmNtCmBgYAoKIyMjIENhbGN1bGF0aW5nIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIHVzaW5nIG9uZS10YWlsZWQgYmlub21pYWwgdGVzdAoKVGhlIGJpbm9taWFsIHRlc3QgaXMgYW4gZXhhY3QgdGVzdCBvZiB0aGUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIG9mIGRldmlhdGlvbnMgZnJvbSBhIHRoZW9yZXRpY2FsbHkgZXhwZWN0ZWQgZGlzdHJpYnV0aW9uIG9mIG9ic2VydmF0aW9ucyBpbnRvIHR3byBjYXRlZ29yaWVzLiBmb3IgdGhpcyBjYXNlIHdlIGNvbnNpZGVyIGVhY2ggY2F0ZWdvcnkvY2xhc3MgYWdhaW5zdCB0aGUgcmVzdC4gVGhlIGNsYXNpY2FsIG9uZS12cy1hbGwgYXBwcm9hY2ggZm9yIGRlYWxpbmcgd2l0aCBtdWx0aWNsYXNzZXMgc2l0dWF0aW9ucwoKIyMjIyBTdWxjdXMKYGBge3J9CmJpbm9tLnRlc3QoY20kdGFibGVbMywzXSAsY20kdGFibGVbMTozLDNdICU+JSBzdW0oKSkKYGBgCgojIyMjIENpbGlhcnkgQm9keSBQb3N0ZXJpb3IKYGBge3J9CmJpbm9tLnRlc3QoY20kdGFibGVbMiwyXSAsY20kdGFibGVbMTozLDJdICU+JSBzdW0oKSkKYGBgCgojIyMjIENpbGlhcnkgQm9keSBBbnRlcmlvcgpgYGB7cn0KYmlub20udGVzdChjbSR0YWJsZVsxLDFdICxjbSR0YWJsZVsxOjMsMV0gJT4lIHN1bSgpKQoKYGBgCgoKCgojIyMgQW5hbHlzaXMgb2YgcnVsZXMgZm91bmQgYnkgQ0FSVAoKIyMjIyBMT09DVgoKU2ltaWxhciB0byBDViB3aXRoIEsgPSBOLCB3aGVyZSBOIGlzIHRoZSBzaXplIG9mIHRoZSBkYXRhc2V0LiBJZGVhbGx5LCB0aGlzIGFwcHJvYWNoIGhhcyBhIGxvdyB2YXJpYW5jZSBpbiB0aGUgcmVzdWx0cy4KCiMjIyMjICBOdW1iZXIgb2YgcnVsZXMgcGVyIG1vZGVsIGRpc3RyaWJ1dGlvbgpgYGB7cn0KcmVzYW1wbGVzPC1sb29fY3YocmV0cm9wb3N0aW9uX2RhdGEpCnBsb3RfbnJ1bGVzKHJlc2FtcGxlcywiTl9QT1NJVElPTn5OT1JNX05fUkVUUk9QT1NJVElPTiIpCmBgYAoKIyMjIyMgUmFuZ2UgdmFyaWF0aW9uIGZvciBtb2RlbHMgd2l0aCBydWxzZXQgc2l6ZSA9IDMKClJ1bGVzIGNhbiBoYXZlIGFuIHVwcGVyIGxpbWl0ICg+PSksIGxvd2VyIGxpbWl0KDwpIG9yIGJvdGggKGlzKQpgYGB7cn0KCnBsb3RfcmFuZ2VzKHJlc2FtcGxlcywiTl9QT1NJVElPTn5OT1JNX05fUkVUUk9QT1NJVElPTiIpCmBgYAoKIyMjIyMgUmFuZ2UgT3ZlcmxhcGluZyBmb3IgcnVsZXNldCBzaXplID0gMwoKYGBge3J9CnBsb3RfcnVsZXNfb3ZlcmxhcGluZyhyZXNhbXBsZXMsIk5fUE9TSVRJT05+Tk9STV9OX1JFVFJPUE9TSVRJT04iKQpgYGAKCiMjIyMgQk9PVFNUUkFQIAoKV2UgZ2VuZXJhdGUgMjAwMCBuZXcgc2FtcGxlcyB3aXRoIHJlcGxhY2VtZW50IGFuZCAgd2UgYnVpbGQgMjAwMCBuZXcgQ0FSVHMuCgojIyMjIyAgTnVtYmVyIG9mIHJ1bGVzIHBlciBtb2RlbCBkaXN0cmlidXRpb24KYGBge3J9CnJlc2FtcGxlczwtYm9vdHN0cmFwcyhyZXRyb3Bvc3Rpb25fZGF0YSxzdHJhdGEgPSBOX1BPU0lUSU9OLHRpbWVzID0gMjAwMCAgKQpwbG90X25ydWxlcyhyZXNhbXBsZXMsIk5fUE9TSVRJT05+Tk9STV9OX1JFVFJPUE9TSVRJT04iKQpgYGAKCiMjIyMjIFJhbmdlIHZhcmlhdGlvbiBmb3IgbW9kZWxzIHdpdGggcnVsc2V0IHNpemUgPSAzClJ1bGVzIGNhbiBoYXZlIGFuIHVwcGVyIGxpbWl0ICg+KSwgbG93ZXIgbGltaXQoPCkgb3IgYm90aCAoaXMpCmBgYHtyfQoKcGxvdF9yYW5nZXMocmVzYW1wbGVzLCJOX1BPU0lUSU9Ofk5PUk1fTl9SRVRST1BPU0lUSU9OIikKYGBgCgojIyMjIyBSYW5nZSBPdmVybGFwaW5nIGZvciBydWxlc2V0IHNpemUgPSAzCgpgYGB7cn0KcGxvdF9ydWxlc19vdmVybGFwaW5nKHJlc2FtcGxlcywiTl9QT1NJVElPTn5OT1JNX05fUkVUUk9QT1NJVElPTiIpCmBgYAoKCiMgVEVNUE9SQUwgUE9TSVRJT04KCiMjIyBDQVJUIG1vZGVsCgpgYGB7cn0KdHJlZSA8LQogIHJwYXJ0OjpycGFydCgKICAgIFRfUE9TSVRJT04gfiBOT1JNX1RfUkVUUk9QT1NJVElPTiwKICAgIGRhdGEgPSByZXRyb3Bvc3Rpb25fZGF0YSwKICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMTAsIHh2YWwgPSAxMCkKICApCnJwYXJ0LnBsb3QoCiAgdHJlZSwKICB0eXBlID0gMSwKICBleHRyYSA9IDEwMSwKICBib3gucGFsZXR0ZSA9ICJHbkJ1IiwKICBicmFuY2gubHR5ID0gMywKICBzaGFkb3cuY29sID0gImdyYXkiLAogIG5uID0gVFJVRQopCnByaW50Y3AodHJlZSkKcnBhcnQucnVsZXModHJlZSkKCgpwcmVkcyA8LSBwcmVkaWN0KHRyZWUsIHJldHJvcG9zdGlvbl9kYXRhLCB0eXBlID0gJ2NsYXNzJykKY20gPC0KICBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHByZWRzLCBhcy5mYWN0b3IocmV0cm9wb3N0aW9uX2RhdGEkVF9QT1NJVElPTikpCmNtCmBgYAoKIyMjIENhbGN1bGF0aW5nIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIHVzaW5nIG9uZS10YWlsZWQgYmlub21pYWwgdGVzdAoKIyMjIyBTdWxjdXMKYGBge3J9CmJpbm9tLnRlc3QoY20kdGFibGVbMywzXSAsY20kdGFibGVbMTozLDNdICU+JSBzdW0oKSkKYGBgCgojIyMjIENpbGlhcnkgQm9keSBQb3N0ZXJpb3IKYGBge3J9CmJpbm9tLnRlc3QoY20kdGFibGVbMiwyXSAsY20kdGFibGVbMTozLDJdICU+JSBzdW0oKSkKYGBgCgojIyMjIENpbGlhcnkgQm9keSBBbnRlcmlvcgpgYGB7cn0KYmlub20udGVzdChjbSR0YWJsZVsxLDFdICxjbSR0YWJsZVsxOjMsMV0gJT4lIHN1bSgpKQoKYGBgCgoKIyMjIEFuYWx5c2lzIG9mIHJ1bGVzIGZvdW5kIGJ5IENBUlQKCiMjIyMgTE9PQ1YKClNpbWlsYXIgdG8gQ1Ygd2l0aCBLID0gTiwgd2hlcmUgTiBpcyB0aGUgc2l6ZSBvZiB0aGUgZGF0YXNldC4gSWRlYWxseSwgdGhpcyBhcHByb2FjaCBoYXMgYSBsb3cgdmFyaWFuY2UgaW4gdGhlIHJlc3VsdHMuCgojIyMjIyAgTnVtYmVyIG9mIHJ1bGVzIHBlciBtb2RlbCBkaXN0cmlidXRpb24KYGBge3J9CnJlc2FtcGxlczwtbG9vX2N2KHJldHJvcG9zdGlvbl9kYXRhKQpwbG90X25ydWxlcyhyZXNhbXBsZXMsIlRfUE9TSVRJT05+Tk9STV9UX1JFVFJPUE9TSVRJT04iKQpgYGAKCiMjIyMjIFJhbmdlIHZhcmlhdGlvbiBmb3IgbW9kZWxzIHdpdGggcnVsc2V0CgpSdWxlcyBjYW4gaGF2ZSBhbiB1cHBlciBsaW1pdCAoPj0pLCBsb3dlciBsaW1pdCg8KSBvciBib3RoIChpcykKKCopICBSdWxlc2V0IHNob3VsZCBpbmNsdWRlIGF0IGxlYXN0IG9uZSBydWxlIHdpdGggdXBwZXIgYW5kIGxvd2VyIGxpbWl0CmBgYHtyfQoKcGxvdF9yYW5nZXMocmVzYW1wbGVzLCJUX1BPU0lUSU9Ofk5PUk1fVF9SRVRST1BPU0lUSU9OIikKYGBgCgojIyMjIyBSYW5nZSBPdmVybGFwaW5nIGZvciBydWxlc2V0IAoKYGBge3J9CnBsb3RfcnVsZXNfb3ZlcmxhcGluZyhyZXNhbXBsZXMsIlRfUE9TSVRJT05+Tk9STV9UX1JFVFJPUE9TSVRJT04iKQpgYGAKCiMjIyMgQk9PVFNUUkFQIAoKV2UgZ2VuZXJhdGUgMjAwMCBuZXcgc2FtcGxlcyB3aXRoIHJlcGxhY2VtZW50IGFuZCAgd2UgYnVpbGQgMjAwMCBuZXcgQ0FSVHMuCgojIyMjIyAgTnVtYmVyIG9mIHJ1bGVzIHBlciBtb2RlbCBkaXN0cmlidXRpb24KYGBge3J9CnJlc2FtcGxlczwtYm9vdHN0cmFwcyhyZXRyb3Bvc3Rpb25fZGF0YSxzdHJhdGEgPSBOX1BPU0lUSU9OLHRpbWVzID0gMjAwMCAgKQpwbG90X25ydWxlcyhyZXNhbXBsZXMsIlRfUE9TSVRJT05+Tk9STV9UX1JFVFJPUE9TSVRJT04iKQpgYGAKCiMjIyMjIFJhbmdlIHZhcmlhdGlvbiBmb3IgbW9kZWxzIHdpdGggcnVsZXNldCAKClJ1bGVzIGNhbiBoYXZlIGFuIHVwcGVyIGxpbWl0ICg+KSwgbG93ZXIgbGltaXQoPCkgb3IgYm90aCAoaXMpCigqKSAgUnVsZXNldCBzaG91bGQgaW5jbHVkZSBhdCBsZWFzdCBvbmUgcnVsZSB3aXRoIHVwcGVyIGFuZCBsb3dlciBsaW1pdApgYGB7cn0KCnBsb3RfcmFuZ2VzKHJlc2FtcGxlcywiVF9QT1NJVElPTn5OT1JNX1RfUkVUUk9QT1NJVElPTiIpCmBgYAoKIyMjIyMgUmFuZ2UgT3ZlcmxhcGluZyBmb3IgcnVsZXNldCAKCgpgYGB7cn0KcGxvdF9ydWxlc19vdmVybGFwaW5nKHJlc2FtcGxlcywiVF9QT1NJVElPTn5OT1JNX1RfUkVUUk9QT1NJVElPTiIpCmBgYAoKCgoK