Reading and preprocesing datataset

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