1 Read the data

library(tidyverse)
library(data.table)
library(dplyr)
library(readxl)
library(caret)
library(magrittr)
ptm<-proc.time()
f<-"/home/bizon/Documents/nuMoM2b_Dataset_NICHD_Data_Challenge.csv"
mu_data_df <- fread(f)
Warning in fread(f) :
  Detected 11717 column names but the data has 11633 columns. Filling rows automatically. Set fill=TRUE explicitly to avoid this warning.
proc.time()-ptm
   user  system elapsed 
  5.312   0.246   2.064 

1.1 Set up easy in-the-app viewing of the accompanying coding and info spreadsheets

I want to be able to look up quickly and select the correct predictor and response variables directly inside this R Studio environment. Eventually, this could be extended to allow variable selection to pass them on to “predictors” and “response” variables used in the various machine learning (ML) models below.

For now, we are simply manually selecting the clinically/medically meaningful predictors/response variables, check them for artifacts, clean up those artifacts as appropriate and proceed to ML modeling to test our hypotheses.


ptm<-proc.time()
f1_info<-"/home/bizon/Documents/mu2b/nuMoM2b_Dataset_Information.xlsx"
f2_code<-"/home/bizon/Documents/mu2b/nuMoM2b_Codebook_NICHD_Data_Challenge.xlsx"
mu_info_df <- read_excel(f1_info)
New names:
* `` -> ...2
* `` -> ...3
* `` -> ...4
* `` -> ...5
* `` -> ...6
* ...
mu_code_df <- read_excel(f2_code)
proc.time()-ptm
   user  system elapsed 
  0.092   0.013   0.101 

Now that we loaded our Information and Code spreadsheets, we can use DT library to view and search in them easily.

Note: it is best to open the output tables in a new window.

library(DT)
datatable(mu_code_df, filter = 'top', options = list(pageLength = 10, autoWidth = TRUE))

2 Preprocessing

I need to remove NA and non-sense values from CMAE04a4c.

Note: this step can be repeated as needed in the preprocessing pipeline for the desired response variable

# select "CMAE04a4c" | if NA, remove that row 

# remove all rows where the column CMAJ01 has NA
clean_CMAE04a4c_df<-mu_data_df[!is.na(mu_data_df$CMAE04a4c), ] 

# The above won't work on an h2o object, but works on a regular dataframe, so reading as df first and then, once preprocessing is done, moving on to h2o

glimpse(clean_CMAE04a4c_df$CMAE04a4c)
 int [1:8792] 0 0 0 0 0 0 0 0 0 0 ...
count(clean_CMAE04a4c_df, CMAE04a4c)
nrow(clean_CMAE04a4c_df[mu_data_df$CMAE04a4c])
[1] 507
sum(is.na(clean_CMAE04a4c_df$CMAE04a4c))
[1] 0

Only ten cases where PTSD postpartum CMAE04a4c=1. Not enough data for the ML approach at this time.

Exploring CMAE04a1c (postpartum depression).

# select "CMAE04a1c" | if NA, remove that row 

# remove all rows where the column CMAJ01 has NA
clean_CMAE04a1c_df<-mu_data_df[!is.na(mu_data_df$CMAE04a1c), ] 

# The above won't work on an h2o object, but works on a regular dataframe, so reading as df first and then, once preprocessing is done, moving on to h2o

glimpse(clean_CMAE04a1c_df$CMAE04a1c)
 int [1:8792] 0 0 0 0 0 0 0 0 0 0 ...
count(clean_CMAE04a1c_df, CMAE04a1c)
nrow(clean_CMAE04a1c_df[mu_data_df$CMAE04a1c])
[1] 835
sum(is.na(clean_CMAE04a1c_df$CMAE04a1c))
[1] 0

There are n=338 positive outcomes, so will pursue this in the following with the ML approaches as done for the re-hospitalization outcome before.

library(h2o)
h2o.init()
 Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         2 hours 19 minutes 
    H2O cluster timezone:       America/Vancouver 
    H2O data parsing timezone:  UTC 
    H2O cluster version:        3.34.0.3 
    H2O cluster version age:    5 days  
    H2O cluster name:           H2O_started_from_R_bizon_rik192 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   11.02 GB 
    H2O cluster total cores:    16 
    H2O cluster allowed cores:  16 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        54321 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    H2O API Extensions:         Amazon S3, XGBoost, Algos, AutoML, Core V3, TargetEncoder, Core V4 
    R Version:                  R version 4.1.1 (2021-08-10) 
ptm<-proc.time()
mu_data <- as.h2o(clean_CMAE04a1c_df)

  |                                                                                                                    
  |                                                                                                              |   0%
  |                                                                                                                    
  |==============================================================================================================| 100%
proc.time()-ptm
   user  system elapsed 
  5.863   0.478  12.571 

mu_data[,"CMAE04a1c"]<-as.factor(mu_data[,"CMAE04a1c"]) 

# predictors "participant", "demographics" dataset (column A) includes the relevant features
# concludes the variables from "demographics" dataset; model as is and test later by adding participant features from the "CMA" set

predictors<-c("CRace",
              "Race",
              "eRace",
              "eHispanic",
              "BMI",
              "BMI_Cat",
              "Education",
              "GravCat",
              "SmokeCat1",
              "SmokeCat2",
              "SmokeCat3",
              "Ins_Govt",
              "Ins_Mil",
              "Ins_Comm",
              "Ins_Pers",
              "Ins_Othr",
              "PctFedPoverty",
              "poverty",
              "V1AD02g",                    #adding psych predictors here
              "CMAE04a1b",
              "CMAE04a4a", 
              "CMAE04a4b"
              ) #makes a list of predicting variables

response<-"CMAE04a1c"

3 ML modeling

3.1 Train the DRF model

(first, let’s do a “quick&dirty” way = no hold-out dataset to get some sense of the data from ML standpoint)


test2_mother_mu_data_RFmodel<-h2o.randomForest(x=predictors,y=response,training_frame = mu_data, nfolds=10,seed = 1234)

  |                                                                                                                    
  |                                                                                                              |   0%
  |                                                                                                                    
  |=======                                                                                                       |   7%
  |                                                                                                                    
  |=======================                                                                                       |  21%
  |                                                                                                                    
  |======================================                                                                        |  35%
  |                                                                                                                    
  |=======================================================                                                       |  50%
  |                                                                                                                    
  |====================================================================================================          |  91%
  |                                                                                                                    
  |==============================================================================================================| 100%

test2_mother_mu_data_RFmodel
Model Details:
==============

H2OBinomialModel: drf
Model ID:  DRF_model_R_1634062886437_2 
Model Summary: 


H2OBinomialMetrics: drf
** Reported on training data. **
** Metrics reported on Out-Of-Bag training samples **

MSE:  0.01885464
RMSE:  0.1373122
LogLoss:  0.117594
Mean Per-Class Error:  0.1108243
AUC:  0.9346045
AUCPR:  0.6157573
Gini:  0.869209
R^2:  0.4899476

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

H2OBinomialMetrics: drf
** Reported on cross-validation data. **
** 10-fold cross-validation on training data (Metrics computed for combined holdout predictions) **

MSE:  0.01800805
RMSE:  0.1341941
LogLoss:  0.1028235
Mean Per-Class Error:  0.09555821
AUC:  0.9277543
AUCPR:  0.6235033
Gini:  0.8555087
R^2:  0.5128496

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
Cross-Validation Metrics Summary: 
AUC:  0.9346045
AUCPR:  0.6157573

That was a quick test.

Now I need to create a hold-out dataset first; repeat the above then as test:validation.

3.2 Split the data 0.8 for training:validation

mother2_mu_data_split <- h2o.splitFrame(mu_data, ratios=0.8, seed = 1)
train=mother2_mu_data_split[[1]]
valid=mother2_mu_data_split[[2]]

3.3 Train the DRF model on the training dataset = 0.8

ptm<-proc.time()
mother2_mu_data_RFmodel<-h2o.randomForest(x=predictors,y=response,training_frame = train, nfolds=10,seed = 1234)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |================                                                                    |  20%
  |                                                                                          
  |===============================                                                     |  37%
  |                                                                                          
  |==============================================                                      |  55%
  |                                                                                          
  |=============================================================                       |  73%
  |                                                                                          
  |==================================================================================  |  97%
  |                                                                                          
  |====================================================================================| 100%
proc.time()-ptm
   user  system elapsed 
  0.560   0.021   6.879 

3.4 Predict using the DRF model on the testing dataset =0.2

Yields AUC: 0.9288383 and AUCPR: 0.6338817

ptm<-proc.time()

mother2_mu_data_predict<-h2o.predict(object=mother2_mu_data_RFmodel, newdata=valid)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |====================================================================================| 100%
mother2_mu_data_RFmodel
Model Details:
==============

H2OBinomialModel: drf
Model ID:  DRF_model_R_1634062886437_658 
Model Summary: 


H2OBinomialMetrics: drf
** Reported on training data. **
** Metrics reported on Out-Of-Bag training samples **

MSE:  0.0181672
RMSE:  0.1347857
LogLoss:  0.119153
Mean Per-Class Error:  0.117894
AUC:  0.9235196
AUCPR:  0.6503907
Gini:  0.8470391
R^2:  0.5034799

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

H2OBinomialMetrics: drf
** Reported on cross-validation data. **
** 10-fold cross-validation on training data (Metrics computed for combined holdout predictions) **

MSE:  0.01771996
RMSE:  0.1331163
LogLoss:  0.09748087
Mean Per-Class Error:  0.0900565
AUC:  0.9288383
AUCPR:  0.6338817
Gini:  0.8576766
R^2:  0.5157031

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
Cross-Validation Metrics Summary: 
proc.time()-ptm
   user  system elapsed 
  0.080   0.005   0.111 

3.4.1 AUROCpr for predicting re-hospitalization based on patient demographics alone


mod=mother2_mu_data_RFmodel

perf <- h2o.performance(mod,valid)

metrics <- as.data.frame(h2o.metric(perf))

metrics

metrics %>%
  ggplot(aes(recall,precision)) + 
  geom_line() +
  theme_minimal()


metrics %>%
  ggplot(aes(precision, accuracy)) + 
  geom_line() +
  theme_minimal()

3.4.2 Explain the model


ptm<-proc.time()

# toggle progress bar if desired:
# h2o.show_progress() 

exp <-h2o.explain(object=mother2_mu_data_RFmodel, newdata=valid)

3.4.3 Statistics summary


results_df <- function(h2o_model) {
  h2o_model@model$cross_validation_metrics_summary %>% 
    as.data.frame() %>% 
    select(-mean, -sd) %>% 
    t() %>% 
    as.data.frame() %>% 
    mutate_all(as.character) %>% 
    mutate_all(as.numeric) -> k
  
  k %>% 
    select(Accuracy = accuracy,
           AUC = auc,
           Precision = precision,
           Specificity = specificity,
           Recall = recall,
           Logloss = logloss) %>% 
  return()
}

# Using function
results_df(mod) -> outcome

# Outcome 
outcome %>% 
  gather(Metrics, Values) %>% 
  ggplot(aes(Metrics, Values, fill = Metrics, color = Metrics)) +
  geom_boxplot(alpha = 0.3, show.legend = FALSE) + 
  facet_wrap(~ Metrics, scales = "free") + 
  labs(title = "Performance of our ML model using H2o package ",
       caption = "Data Source: NICHD Decoding Maternal Morbidity Data Challenge\nCreated by Martin Frasch (further credit to https://bit.ly/3BpPqcb)") +
  theme_minimal()


# Statistics summary
outcome %>% 
  gather(Metrics, Values) %>% 
  group_by(Metrics) %>% 
  summarise_each(funs(mean, median, min, max, sd, n())) %>% 
  mutate_if(is.numeric, function(x) {round(100*x, 2)}) %>%
  knitr::kable(col.names = c("Criterion", "Mean", "Median", "Min", "Max", "SD", "N"))
Warning: `summarise_each_()` was deprecated in dplyr 0.7.0.
Please use `across()` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
Warning: `funs()` was deprecated in dplyr 0.8.0.
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
Criterion Mean Median Min Max SD N
Accuracy 97.93 97.77 97.28 98.70 0.45 1000
AUC 93.41 93.81 87.33 99.12 3.72 1000
Logloss 9.74 10.01 4.79 15.68 3.59 1000
Precision 68.19 64.58 59.26 80.77 8.26 1000
Recall 83.46 81.63 71.43 100.00 8.95 1000
Specificity 98.48 98.51 98.12 99.33 0.37 1000

3.5 Building an interpretable decision tree model


maternal_dt_model<-h2o.gbm(x=predictors,y=response,training_frame = train, validation_frame = valid, balance_classes = TRUE, seed = 1234, nfolds=10)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |=========                                                                           |  11%
  |                                                                                          
  |========================                                                            |  28%
  |                                                                                          
  |======================================                                              |  45%
  |                                                                                          
  |=====================================================                               |  64%
  |                                                                                          
  |=============================================================================       |  91%
  |                                                                                          
  |====================================================================================| 100%
# GBM hyperparamters
gbm_params = list(max_depth = seq(2, 10))

# Train and validate a cartesian grid of GBMs
gbm_grid = h2o.grid("gbm", x = predictors, y = response,
                    grid_id = "gbm_grid_1tree8",
                    training_frame = train,
                    validation_frame = valid,
                    balance_classes = TRUE,
                    ntrees = 1, min_rows = 1, sample_rate = 1, col_sample_rate = 1,
                    learn_rate = .01, seed = 1234, 
                    hyper_params = gbm_params)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |====================================================================================| 100%
gbm_gridperf = h2o.getGrid(grid_id = "gbm_grid_1tree8",
                           sort_by = "auc",
                           decreasing = TRUE)

# what is the performance of this GBM?
maternal_dt_model
Model Details:
==============

H2OBinomialModel: gbm
Model ID:  GBM_model_R_1634062886437_1556 
Model Summary: 


H2OBinomialMetrics: gbm
** Reported on training data. **

MSE:  0.1187193
RMSE:  0.3445567
LogLoss:  0.4121877
Mean Per-Class Error:  0.02596636
AUC:  0.9941941
AUCPR:  0.9926069
Gini:  0.9883883
R^2:  0.5251227

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
H2OBinomialMetrics: gbm
** Reported on validation data. **

MSE:  0.02092163
RMSE:  0.1446431
LogLoss:  0.07845379
Mean Per-Class Error:  0.1021054
AUC:  0.9468121
AUCPR:  0.5202862
Gini:  0.8936243
R^2:  0.4563593

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
H2OBinomialMetrics: gbm
** Reported on cross-validation data. **
** 10-fold cross-validation on training data (Metrics computed for combined holdout predictions) **

MSE:  0.01704429
RMSE:  0.1305538
LogLoss:  0.06699951
Mean Per-Class Error:  0.08252005
AUC:  0.9473067
AUCPR:  0.6510553
Gini:  0.8946135
R^2:  0.5341696

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
Cross-Validation Metrics Summary: 

We obtain AUC: 0.9473067, AUCPR: 0.6510553

gbm_gridperf
H2O Grid Details
================

Grid ID: gbm_grid_1tree8 
Used hyper parameters: 
  -  max_depth 
Number of models: 9 
Number of failed models: 0 

Hyper-Parameter Search Summary: ordered by decreasing auc

Inflection point is at max_depth=5


maternal_1_tree = h2o.gbm(x = predictors, y = response, 
                        training_frame = train, balance_classes = TRUE,
                        ntrees = 1, min_rows = 1, sample_rate = 1, col_sample_rate = 1,
                        max_depth = 5,
                  # use early stopping once the validation AUC doesn't improve by at least 0.01%
                  # for 5 consecutive scoring events
                        stopping_rounds = 3, stopping_tolerance = 0.01, 
                        stopping_metric = "AUC", 
                        seed = 1)
Warning in .h2o.processResponseWarnings(res) :
  early stopping is enabled but neither score_tree_interval or score_each_iteration are defined. Early stopping will not be reproducible!.

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |====================================================================================| 100%
maternal_1_tree
Model Details:
==============

H2OBinomialModel: gbm
Model ID:  GBM_model_R_1634062886437_2476 
Model Summary: 


H2OBinomialMetrics: gbm
** Reported on training data. **

MSE:  0.4571694
RMSE:  0.676143
LogLoss:  1.575699
Mean Per-Class Error:  0.06445889
AUC:  0.9717425
AUCPR:  0.9703432
Gini:  0.943485
R^2:  -0.8286777

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
AUCPR:  0.882762
maternal_Tree = h2o.getModelTree(model = maternal_1_tree, tree_number = 1)

# Visualizing H2O Trees

library(data.tree)

createDataTree <- function(h2oTree) {
  
  h2oTreeRoot = h2oTree@root_node
  
  dataTree = Node$new(h2oTreeRoot@split_feature)
  dataTree$type = 'split'
  
  addChildren(dataTree, h2oTreeRoot)
  
  return(dataTree)
}

addChildren <- function(dtree, node) {
  
  if(class(node)[1] != 'H2OSplitNode') return(TRUE)
  
  feature = node@split_feature
  id = node@id
  na_direction = node@na_direction
  
  if(is.na(node@threshold)) {
    leftEdgeLabel = printValues(node@left_levels, na_direction=='LEFT', 4)
    rightEdgeLabel = printValues(node@right_levels, na_direction=='RIGHT', 4)
  }else {
    leftEdgeLabel = paste("<", node@threshold, ifelse(na_direction=='LEFT',',NA',''))
    rightEdgeLabel = paste(">=", node@threshold, ifelse(na_direction=='RIGHT',',NA',''))
  }
  
  left_node = node@left_child
  right_node = node@right_child
  
  if(class(left_node)[[1]] == 'H2OLeafNode')
    leftLabel = paste("prediction:", left_node@prediction)
  else
    leftLabel = left_node@split_feature
  
  if(class(right_node)[[1]] == 'H2OLeafNode')
    rightLabel = paste("prediction:", right_node@prediction)
  else
    rightLabel = right_node@split_feature
  
  if(leftLabel == rightLabel) {
    leftLabel = paste(leftLabel, "(L)")
    rightLabel = paste(rightLabel, "(R)")
  }
  
  dtreeLeft = dtree$AddChild(leftLabel)
  dtreeLeft$edgeLabel = leftEdgeLabel
  dtreeLeft$type = ifelse(class(left_node)[1] == 'H2OSplitNode', 'split', 'leaf')
  
  dtreeRight = dtree$AddChild(rightLabel)
  dtreeRight$edgeLabel = rightEdgeLabel
  dtreeRight$type = ifelse(class(right_node)[1] == 'H2OSplitNode', 'split', 'leaf')
  
  addChildren(dtreeLeft, left_node)
  addChildren(dtreeRight, right_node)
  
  return(FALSE)
}

printValues <- function(values, is_na_direction, n=4) {
  l = length(values)
  
  if(l == 0)
    value_string = ifelse(is_na_direction, "NA", "")
  else
    value_string = paste0(paste0(values[1:min(n,l)], collapse = ', '),
                          ifelse(l > n, ",...", ""),
                          ifelse(is_na_direction, ", NA", ""))
  
  return(value_string)
}

This decision tree, also supplied as PDF, is meant to help build intuition about how the model.


library(DiagrammeR)

# customized DT for our H2O model

maternal_mu2DataTree = createDataTree(maternal_Tree)

GetEdgeLabel <- function(node) {return (node$edgeLabel)}
GetNodeShape <- function(node) {switch(node$type, 
                                       split = "diamond", leaf = "oval")}
GetFontName <- function(node) {switch(node$type, 
                                      split = 'Palatino-bold', 
                                      leaf = 'Palatino')}
SetEdgeStyle(maternal_mu2DataTree, fontname = 'Palatino-italic', 
             label = GetEdgeLabel, labelfloat = TRUE,
             fontsize = "26", fontcolor='royalblue4')
SetNodeStyle(maternal_mu2DataTree, fontname = GetFontName, shape = GetNodeShape, 
             fontsize = "26", fontcolor='royalblue4',
             height="0.75", width="1")

SetGraphStyle(maternal_mu2DataTree, rankdir = "LR", dpi=70.)

plot(maternal_mu2DataTree, output = "graph")
NA
ptm<-proc.time()

exp_dt<-h2o.explain(maternal_dt_model,valid)

proc.time()-ptm
   user  system elapsed 
 51.442   1.332  68.781 

exp_dt


Confusion Matrix
================

> Confusion matrix shows a predicted class vs an actual class.



GBM_model_R_1634062886437_1556
------------------------------
Confusion Matrix (vertical: actual; across: predicted)  for max f1 @ threshold = 0.216446697145969:


Variable Importance
===================

> The variable importance plot shows the relative importance of the most important variables in the model.



SHAP Summary
============

> SHAP summary plot shows the contribution of the features for each instance (row of data). The sum of the feature contributions and the bias term is equal to the raw prediction of the model, i.e., prediction before applying inverse link function.



Partial Dependence Plots
========================

> Partial dependence plot (PDP) gives a graphical depiction of the marginal effect of a variable on the response. The effect of a variable is measured in change in the mean response. PDP assumes independence between the feature for which is the PDP computed and the rest.

3.6 Using Naïve Bayes Classifier


# Build and train the model:
mo2b_nb <- h2o.naiveBayes(x = predictors,
                          y = response,
                          training_frame = train,
                          laplace = 0,
                          nfolds = 10,
                          seed = 1234)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |==================                                                                  |  21%
  |                                                                                          
  |====================================================================================| 100%
# Eval performance:
perf <- h2o.performance(mo2b_nb)

# Generate the predictions on a test set (if necessary):
pred <- h2o.predict(mo2b_nb, newdata = valid)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |====================================================================================| 100%
perf
H2OBinomialMetrics: naivebayes
** Reported on training data. **

MSE:  0.04521123
RMSE:  0.2126293
LogLoss:  0.6202355
Mean Per-Class Error:  0.08343609
AUC:  0.9259393
AUCPR:  0.5121705
Gini:  0.8518787

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:

Maximum Metrics: Maximum metrics at their respective thresholds

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

NB model achieves AUC: 0.9259393 and AUCPR: 0.5121705


# best viewed in a new window or see, please, the PDF included with the submission
exp_nb <- h2o.explain(mo2b_nb,valid)
Warning: StackedEnsemble does not have a variable importance. Picking all columns. Set `columns` to a vector of columns to explain just a subset of columns.

Note the highly variable partial importance of the different socio-demographic characteristics


exp_nb


Confusion Matrix
================

> Confusion matrix shows a predicted class vs an actual class.



NaiveBayes_model_R_1634062886437_2480
-------------------------------------
Confusion Matrix (vertical: actual; across: predicted)  for max f1 @ threshold = 0.999997946867559:


Partial Dependence Plots
========================

> Partial dependence plot (PDP) gives a graphical depiction of the marginal effect of a variable on the response. The effect of a variable is measured in change in the mean response. PDP assumes independence between the feature for which is the PDP computed and the rest.

3.7 Extending the ML toolbox: Using h2o AutoML mode to find an objectively best performing model

Compare here. The findings are to be interpreted with caution at this stage. Once we obtain a larger external dataset for validation, with a more balanced case distribution, this will become more useful and allow building an inference engine that could be deployed for use. I am presenting this code therefore as a reference for future work.

Nevertheless, it is evident that an optimization even at this stage results in a classification prediction performance of AUROC = 0.9937349. This result can vary depending on the run.

Note please, this code runs for about 70 min on a well-equipped deep learning workstation.

3.7.1 Train in AutoML mode


ptm<-proc.time()

maternal_aml <- h2o.automl(x=predictors,y=response,training_frame = train, max_models = 20, seed = 1)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |===                                                                                 |   3%
  |                                                                                          
  |=====                                                                               |   6%
  |                                                                                          
  |======                                                                              |   7%
  |                                                                                          
  |========                                                                            |   9%
  |                                                                                          
  |==========                                                                          |  12%
  |                                                                                          
  |============                                                                        |  14%
  |                                                                                          
  |==============                                                                      |  17%
  |                                                                                          
  |================                                                                    |  20%
  |                                                                                          
  |===================                                                                 |  23%
  |                                                                                          
  |=====================                                                               |  25%
  |                                                                                          
  |=================================                                                   |  40%
  |                                                                                          
  |==========================================                                          |  50%
  |                                                                                          
  |==============================================                                      |  54%
  |                                                                                          
  |================================================                                    |  57%
  |                                                                                          
  |====================================================                                |  62%
  |                                                                                          
  |========================================================                            |  67%
  |                                                                                          
  |=============================================================                       |  72%
  |                                                                                          
  |==============================================================                      |  74%
  |                                                                                          
  |================================================================                    |  76%
  |                                                                                          
  |=================================================================                   |  77%
  |                                                                                          
  |==================================================================                  |  79%
  |                                                                                          
  |====================================================================                |  80%
  |                                                                                          
  |====================================================================================| 100%
maternal_lb <- maternal_aml@leaderboard

#print(maternal_lb, n = nrow(maternal_lb)) #Print all rows instead of default 6 rows

proc.time()-ptm
    user   system  elapsed 
  79.778    1.204 1085.929 

3.7.2 Validate the combined leader aml model

ptm<-proc.time()

maternal_perf_valid <- h2o.performance(maternal_aml@leader,newdata=valid,xval=FALSE,valid=TRUE)

pred <- h2o.predict(maternal_aml@leader, valid)

  |                                                                                          
  |                                                                                    |   0%
  |                                                                                          
  |====================================================================================| 100%
h2o.auc(maternal_aml@leader)
[1] 0.9937349

Explain the model

We observe no specificity because the dataset is unbalanced such that by luck of draw (when the dataset is split 80:20) we get no true positives.

ptm<-proc.time()
exp <-h2o.explain(maternal_aml@leader, valid)
Warning: StackedEnsemble does not have a variable importance. Picking all columns. Set `columns` to a vector of columns to explain just a subset of columns.
proc.time()-ptm
   user  system elapsed 
  5.311   0.429  54.001 
print(exp)


Confusion Matrix
================

> Confusion matrix shows a predicted class vs an actual class.



StackedEnsemble_BestOfFamily_2_AutoML_1_20211012_125843
-------------------------------------------------------
Confusion Matrix (vertical: actual; across: predicted)  for max f1 @ threshold = 0.411276621237627:


Partial Dependence Plots
========================

> Partial dependence plot (PDP) gives a graphical depiction of the marginal effect of a variable on the response. The effect of a variable is measured in change in the mean response. PDP assumes independence between the feature for which is the PDP computed and the rest.

sessionInfo()
R version 4.1.1 (2021-08-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.6 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/libopenblasp-r0.2.20.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
 [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] DiagrammeR_1.0.6.1 data.tree_1.0.0    ggcorrplot_0.1.3   h2o_3.34.0.3      
 [5] DT_0.19            magrittr_2.0.1     caret_6.0-90       lattice_0.20-45   
 [9] readxl_1.3.1       data.table_1.14.2  forcats_0.5.1      stringr_1.4.0     
[13] dplyr_1.0.7        purrr_0.3.4        readr_2.0.2        tidyr_1.1.4       
[17] tibble_3.1.5       ggplot2_3.3.5      tidyverse_1.3.1   

loaded via a namespace (and not attached):
 [1] nlme_3.1-153         bitops_1.0-7         fs_1.5.0             bit64_4.0.5         
 [5] lubridate_1.8.0      RColorBrewer_1.1-2   httr_1.4.2           bslib_0.3.1         
 [9] tools_4.1.1          backports_1.2.1      utf8_1.2.2           R6_2.5.1            
[13] rpart_4.1-15         DBI_1.1.1            colorspace_2.0-2     nnet_7.3-16         
[17] withr_2.4.2          tidyselect_1.1.1     bit_4.0.4            compiler_4.1.1      
[21] cli_3.0.1            rvest_1.0.1          xml2_1.3.2           labeling_0.4.2      
[25] sass_0.4.0           scales_1.1.1         digest_0.6.28        rmarkdown_2.11      
[29] pkgconfig_2.0.3      htmltools_0.5.2      parallelly_1.28.1    highr_0.9           
[33] dbplyr_2.1.1         fastmap_1.1.0        htmlwidgets_1.5.4    rlang_0.4.11        
[37] rstudioapi_0.13      visNetwork_2.1.0     farver_2.1.0         jquerylib_0.1.4     
[41] generics_0.1.0       jsonlite_1.7.2       ModelMetrics_1.2.2.2 RCurl_1.98-1.5      
[45] Matrix_1.3-4         Rcpp_1.0.7           munsell_0.5.0        fansi_0.5.0         
[49] lifecycle_1.0.1      yaml_2.2.1           stringi_1.7.5        pROC_1.18.0         
[53] MASS_7.3-54          plyr_1.8.6           recipes_0.1.17       grid_4.1.1          
[57] parallel_4.1.1       listenv_0.8.0        crayon_1.4.1         haven_2.4.3         
[61] splines_4.1.1        hms_1.1.1            knitr_1.36           pillar_1.6.3        
[65] future.apply_1.8.1   reshape2_1.4.4       codetools_0.2-18     stats4_4.1.1        
[69] reprex_2.0.1         glue_1.4.2           evaluate_0.14        renv_0.14.0         
[73] modelr_0.1.8         vctrs_0.3.8          tzdb_0.1.2           foreach_1.5.1       
[77] cellranger_1.1.0     gtable_0.3.0         future_1.22.1        assertthat_0.2.1    
[81] xfun_0.26            gower_0.2.2          prodlim_2019.11.13   broom_0.7.9         
[85] class_7.3-19         survival_3.2-13      timeDate_3043.102    iterators_1.0.13    
[89] lava_1.6.10          globals_0.14.0       ellipsis_0.3.2       ipred_0.9-12        
LS0tCnRpdGxlOiAiTklDSEQgRGVjb2RpbmcgTWF0ZXJuYWwgTW9yYmlkaXR5IERhdGEgQ2hhbGxlbmdlOiBwcmVkaWN0aW5nIHBvc3QtcGFydHVtIGRlcHJlc3Npb24gYW5kIGFzc2Vzc2luZyB0aGUgc29jaW9lY29ub21pYyBpbXBhY3QiCmF1dGhvcjogCiAgbmFtZTogIkRyLiBNYXJ0aW4gRy4gRnJhc2NoIgogIGFmZmlsaWF0aW9uOiAiSGVhbHRoIFN0cmVhbSBBbmFseXRpY3MiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdGhlbWU6IHBhcGVyCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIGtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFLCBtZXNzYWdlPVRSVUUsIGluY2x1ZGU9VFJVRSwgd2FybmluZ3MgPSBGQUxTRSwgZGV2PSJwbmciLGRwaT0zMDApCgprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCiMgYmUgY2FyZWZ1bCB3aXRoIGNhY2hlIHNldHRpbmc6IHNldCB0byBGQUxTRSBpZiB5b3Ugd2FudCB0byByZXJ1biBhbGwgY29kZSBiZWZvcmUga25pdHRpbmcKIyBtb3JlIGluZm86IGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi1jb29rYm9vay9jYWNoZS5odG1sCgojIHN5bnRheCB0byB0aW1lIGV4ZWN1dGlvbiBvZiBhIGNlbGwgaW4gUiB8IGFkZGVkIGFzIHRpbWVfaXQgc25pcHBldCB8IGludm9rZSB3aXRoIHNoaWZ0K1RBQgpwdG08LXByb2MudGltZSgpCiMgaW5zZXJ0IG15IGNvZGUKcHJvYy50aW1lKCktcHRtCmBgYAoKYGBge3IsIGluY2x1ZGU9RkFMU0V9CmxvY2FsKHtyIDwtIGdldE9wdGlvbigicmVwb3MiKTsgclsiQ1JBTiJdIDwtICJodHRwOi8vY3Jhbi51cy5yLXByb2plY3Qub3JnIjsgb3B0aW9ucyhyZXBvcyA9IHIpfSkKaWYgKCEiUi51dGlscyIgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkpIGluc3RhbGwucGFja2FnZXMoIlIudXRpbHMiKQpzZXR3ZCgiL2hvbWUvYml6b24vRG9jdW1lbnRzL211MmIvIikKb3B0aW9ucyhlY2hvPVRSVUUpClRFU1RfUk9PVF9ESVIgPC0gIi4uIgpgYGAKCiMgUmVhZCB0aGUgZGF0YQoKYGBge3IgbG9hZCBnZW5lcmFsIHBhY2thZ2VzIGZvciBoYW5kbGluZyBiaWcgZGF0YX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShkcGx5cikKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkobWFncml0dHIpCmBgYAoKYGBge3IgcmVhZCB0aGUgcmF3IGRhdGEsIGNhY2hlPVRSVUV9CnB0bTwtcHJvYy50aW1lKCkKZjwtIi9ob21lL2Jpem9uL0RvY3VtZW50cy9udU1vTTJiX0RhdGFzZXRfTklDSERfRGF0YV9DaGFsbGVuZ2UuY3N2IgptdV9kYXRhX2RmIDwtIGZyZWFkKGYpCnByb2MudGltZSgpLXB0bQpgYGAKCiMjIFNldCB1cCBlYXN5IGluLXRoZS1hcHAgdmlld2luZyBvZiB0aGUgYWNjb21wYW55aW5nIGNvZGluZyBhbmQgaW5mbyBzcHJlYWRzaGVldHMKCkkgd2FudCB0byBiZSBhYmxlIHRvIGxvb2sgdXAgcXVpY2tseSBhbmQgc2VsZWN0IHRoZSBjb3JyZWN0IHByZWRpY3RvciBhbmQgcmVzcG9uc2UgdmFyaWFibGVzIGRpcmVjdGx5IGluc2lkZSB0aGlzIFIgU3R1ZGlvIGVudmlyb25tZW50LiBFdmVudHVhbGx5LCB0aGlzIGNvdWxkIGJlIGV4dGVuZGVkIHRvIGFsbG93IHZhcmlhYmxlIHNlbGVjdGlvbiB0byBwYXNzIHRoZW0gb24gdG8gInByZWRpY3RvcnMiIGFuZCAicmVzcG9uc2UiIHZhcmlhYmxlcyB1c2VkIGluIHRoZSB2YXJpb3VzIG1hY2hpbmUgbGVhcm5pbmcgKE1MKSBtb2RlbHMgYmVsb3cuCgpGb3Igbm93LCB3ZSBhcmUgc2ltcGx5IG1hbnVhbGx5IHNlbGVjdGluZyB0aGUgY2xpbmljYWxseS9tZWRpY2FsbHkgbWVhbmluZ2Z1bCBwcmVkaWN0b3JzL3Jlc3BvbnNlIHZhcmlhYmxlcywgY2hlY2sgdGhlbSBmb3IgYXJ0aWZhY3RzLCBjbGVhbiB1cCB0aG9zZSBhcnRpZmFjdHMgYXMgYXBwcm9wcmlhdGUgYW5kIHByb2NlZWQgdG8gTUwgbW9kZWxpbmcgdG8gdGVzdCBvdXIgaHlwb3RoZXNlcy4KCmBgYHtyIHJlYWQgdGhlIGV4cGxhbmF0b3J5IGZpbGVzIHRvIGhhbmRsZSBldmVyeXRoaW5nIGluIG9uZSBwbGFjZSwgY2FjaGU9VFJVRX0KCnB0bTwtcHJvYy50aW1lKCkKZjFfaW5mbzwtIi9ob21lL2Jpem9uL0RvY3VtZW50cy9tdTJiL251TW9NMmJfRGF0YXNldF9JbmZvcm1hdGlvbi54bHN4IgpmMl9jb2RlPC0iL2hvbWUvYml6b24vRG9jdW1lbnRzL211MmIvbnVNb00yYl9Db2RlYm9va19OSUNIRF9EYXRhX0NoYWxsZW5nZS54bHN4IgptdV9pbmZvX2RmIDwtIHJlYWRfZXhjZWwoZjFfaW5mbykKbXVfY29kZV9kZiA8LSByZWFkX2V4Y2VsKGYyX2NvZGUpCnByb2MudGltZSgpLXB0bQpgYGAKCk5vdyB0aGF0IHdlIGxvYWRlZCBvdXIgSW5mb3JtYXRpb24gYW5kIENvZGUgc3ByZWFkc2hlZXRzLCB3ZSBjYW4gdXNlIERUIGxpYnJhcnkgdG8gdmlldyBhbmQgc2VhcmNoIGluIHRoZW0gZWFzaWx5LgoKKk5vdGU6IGl0IGlzIGJlc3QgdG8gb3BlbiB0aGUgb3V0cHV0IHRhYmxlcyBpbiBhIG5ldyB3aW5kb3cuKgoKYGBge3IgdmlldyBjb2RlIGFuZCBpbmZvIHRhYmxlcywgY2FjaGU9VFJVRX0KbGlicmFyeShEVCkKZGF0YXRhYmxlKG11X2NvZGVfZGYsIGZpbHRlciA9ICd0b3AnLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsIGF1dG9XaWR0aCA9IFRSVUUpKQpkYXRhdGFibGUobXVfaW5mb19kZiwgZmlsdGVyID0gJ3RvcCcsIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMCwgYXV0b1dpZHRoID0gVFJVRSkpCmBgYAoKIyBQcmVwcm9jZXNzaW5nCgpbW0kgbmVlZCB0byByZW1vdmUgTkEgYW5kIG5vbi1zZW5zZSB2YWx1ZXMgZnJvbV17LnVsfV17LnNtYWxsY2Fwc30gW0NNQUUwNGE0Y1sqKi4qKl17LnNtYWxsY2Fwc31dey51bH0KCipOb3RlOiB0aGlzIHN0ZXAgY2FuIGJlIHJlcGVhdGVkIGFzIG5lZWRlZCBpbiB0aGUgcHJlcHJvY2Vzc2luZyBwaXBlbGluZSBmb3IgdGhlIGRlc2lyZWQgcmVzcG9uc2UgdmFyaWFibGUqCgpgYGB7ciByZW1vdmUgTkEgcm93cyBmb3IgQ01BRTA0YTRjLCBjYWNoZT1UUlVFfQojIHNlbGVjdCAiQ01BRTA0YTRjIiB8IGlmIE5BLCByZW1vdmUgdGhhdCByb3cgCgojIHJlbW92ZSBhbGwgcm93cyB3aGVyZSB0aGUgY29sdW1uIENNQUowMSBoYXMgTkEKY2xlYW5fQ01BRTA0YTRjX2RmPC1tdV9kYXRhX2RmWyFpcy5uYShtdV9kYXRhX2RmJENNQUUwNGE0YyksIF0gCgojIFRoZSBhYm92ZSB3b24ndCB3b3JrIG9uIGFuIGgybyBvYmplY3QsIGJ1dCB3b3JrcyBvbiBhIHJlZ3VsYXIgZGF0YWZyYW1lLCBzbyByZWFkaW5nIGFzIGRmIGZpcnN0IGFuZCB0aGVuLCBvbmNlIHByZXByb2Nlc3NpbmcgaXMgZG9uZSwgbW92aW5nIG9uIHRvIGgybwpgYGAKCmBgYHtyIHByZXByb2Nlc3NpbmdfcmVzdWx0c192aWV3X3dpdGhfZHBseXIxLCBlY2hvPVRSVUUsIHBhZ2VkLnByaW50PVRSVUUsIGNhY2hlPVRSVUV9CgpnbGltcHNlKGNsZWFuX0NNQUUwNGE0Y19kZiRDTUFFMDRhNGMpCmNvdW50KGNsZWFuX0NNQUUwNGE0Y19kZiwgQ01BRTA0YTRjKQpucm93KGNsZWFuX0NNQUUwNGE0Y19kZlttdV9kYXRhX2RmJENNQUUwNGE0Y10pCnN1bShpcy5uYShjbGVhbl9DTUFFMDRhNGNfZGYkQ01BRTA0YTRjKSkKYGBgCgpPbmx5IHRlbiBjYXNlcyB3aGVyZSBQVFNEIHBvc3RwYXJ0dW0gQ01BRTA0YTRjPTEuIE5vdCBlbm91Z2ggZGF0YSBmb3IgdGhlIE1MIGFwcHJvYWNoIGF0IHRoaXMgdGltZS4KCkV4cGxvcmluZyBDTUFFMDRhMWMgKHBvc3RwYXJ0dW0gZGVwcmVzc2lvbikuCgpgYGB7ciByZW1vdmUgTkEgcm93cyBmb3IgQ01BRTA0YTFjLCBjYWNoZT1UUlVFfQojIHNlbGVjdCAiQ01BRTA0YTFjIiB8IGlmIE5BLCByZW1vdmUgdGhhdCByb3cgCgojIHJlbW92ZSBhbGwgcm93cyB3aGVyZSB0aGUgY29sdW1uIENNQUowMSBoYXMgTkEKY2xlYW5fQ01BRTA0YTFjX2RmPC1tdV9kYXRhX2RmWyFpcy5uYShtdV9kYXRhX2RmJENNQUUwNGExYyksIF0gCgojIFRoZSBhYm92ZSB3b24ndCB3b3JrIG9uIGFuIGgybyBvYmplY3QsIGJ1dCB3b3JrcyBvbiBhIHJlZ3VsYXIgZGF0YWZyYW1lLCBzbyByZWFkaW5nIGFzIGRmIGZpcnN0IGFuZCB0aGVuLCBvbmNlIHByZXByb2Nlc3NpbmcgaXMgZG9uZSwgbW92aW5nIG9uIHRvIGgybwpgYGAKCmBgYHtyIHByZXByb2Nlc3NpbmdfcmVzdWx0c192aWV3X3dpdGhfZHBseXIyLCBlY2hvPVRSVUUsIHBhZ2VkLnByaW50PVRSVUUsIGNhY2hlPVRSVUV9CgpnbGltcHNlKGNsZWFuX0NNQUUwNGExY19kZiRDTUFFMDRhMWMpCmNvdW50KGNsZWFuX0NNQUUwNGExY19kZiwgQ01BRTA0YTFjKQpucm93KGNsZWFuX0NNQUUwNGExY19kZlttdV9kYXRhX2RmJENNQUUwNGExY10pCnN1bShpcy5uYShjbGVhbl9DTUFFMDRhMWNfZGYkQ01BRTA0YTFjKSkKYGBgCgpUaGVyZSBhcmUgbj0zMzggcG9zaXRpdmUgb3V0Y29tZXMsIHNvIHdpbGwgcHVyc3VlIHRoaXMgaW4gdGhlIGZvbGxvd2luZyB3aXRoIHRoZSBNTCBhcHByb2FjaGVzIGFzIGRvbmUgZm9yIHRoZSByZS1ob3NwaXRhbGl6YXRpb24gb3V0Y29tZSBiZWZvcmUuCgpgYGB7ciBsb2FkIGgyb30KbGlicmFyeShoMm8pCmgyby5pbml0KCkKYGBgCgpgYGB7ciByZWFkX2ludG9faDJvLCBjYWNoZT1UUlVFfQpwdG08LXByb2MudGltZSgpCm11X2RhdGEgPC0gYXMuaDJvKGNsZWFuX0NNQUUwNGExY19kZikKcHJvYy50aW1lKCktcHRtCmBgYAoKYGBge3Igc2V0X3ByZWRpY3RvcnNfcmVzcG9uc2UsIGNhY2hlPVRSVUV9CgptdV9kYXRhWywiQ01BRTA0YTFjIl08LWFzLmZhY3RvcihtdV9kYXRhWywiQ01BRTA0YTFjIl0pIAoKIyBwcmVkaWN0b3JzICJwYXJ0aWNpcGFudCIsICJkZW1vZ3JhcGhpY3MiIGRhdGFzZXQgKGNvbHVtbiBBKSBpbmNsdWRlcyB0aGUgcmVsZXZhbnQgZmVhdHVyZXMKIyBjb25jbHVkZXMgdGhlIHZhcmlhYmxlcyBmcm9tICJkZW1vZ3JhcGhpY3MiIGRhdGFzZXQ7IG1vZGVsIGFzIGlzIGFuZCB0ZXN0IGxhdGVyIGJ5IGFkZGluZyBwYXJ0aWNpcGFudCBmZWF0dXJlcyBmcm9tIHRoZSAiQ01BIiBzZXQKCnByZWRpY3RvcnM8LWMoIkNSYWNlIiwKICAgICAgICAgICAgICAiUmFjZSIsCiAgICAgICAgICAgICAgImVSYWNlIiwKICAgICAgICAgICAgICAiZUhpc3BhbmljIiwKICAgICAgICAgICAgICAiQk1JIiwKICAgICAgICAgICAgICAiQk1JX0NhdCIsCiAgICAgICAgICAgICAgIkVkdWNhdGlvbiIsCiAgICAgICAgICAgICAgIkdyYXZDYXQiLAogICAgICAgICAgICAgICJTbW9rZUNhdDEiLAogICAgICAgICAgICAgICJTbW9rZUNhdDIiLAogICAgICAgICAgICAgICJTbW9rZUNhdDMiLAogICAgICAgICAgICAgICJJbnNfR292dCIsCiAgICAgICAgICAgICAgIkluc19NaWwiLAogICAgICAgICAgICAgICJJbnNfQ29tbSIsCiAgICAgICAgICAgICAgIkluc19QZXJzIiwKICAgICAgICAgICAgICAiSW5zX090aHIiLAogICAgICAgICAgICAgICJQY3RGZWRQb3ZlcnR5IiwKICAgICAgICAgICAgICAicG92ZXJ0eSIsCiAgICAgICAgICAgICAgIlYxQUQwMmciLCAgICAgICAgICAgICAgICAgICAgI2FkZGluZyBwc3ljaCBwcmVkaWN0b3JzIGhlcmUKICAgICAgICAgICAgICAiQ01BRTA0YTFiIiwKICAgICAgICAgICAgICAiQ01BRTA0YTRhIiwgCiAgICAgICAgICAgICAgIkNNQUUwNGE0YiIKICAgICAgICAgICAgICApICNtYWtlcyBhIGxpc3Qgb2YgcHJlZGljdGluZyB2YXJpYWJsZXMKCnJlc3BvbnNlPC0iQ01BRTA0YTFjIgpgYGAKCiMgTUwgbW9kZWxpbmcKCiMjIFRyYWluIHRoZSBEUkYgbW9kZWwKCihmaXJzdCwgbGV0J3MgZG8gYSAicXVpY2smZGlydHkiIHdheSA9IG5vIGhvbGQtb3V0IGRhdGFzZXQgdG8gZ2V0IHNvbWUgc2Vuc2Ugb2YgdGhlIGRhdGEgZnJvbSBNTCBzdGFuZHBvaW50KQoKYGBge3IgZmlyc3RfZHJmX21vZGVsLCBjYWNoZT1UUlVFfQoKIyB1c2VyIG5vdGU6IGlmIHlvdSByZS1zdGFydCBoMm8gSlZNLCB5b3UgbXVzdCByZS1pbXBvcnQgdGhlIGRhdGFzZXQgYW5kIHByZWRpY3RvcnMvcmVzcG9uc2UgdmFycwp0ZXN0Ml9tb3RoZXJfbXVfZGF0YV9SRm1vZGVsPC1oMm8ucmFuZG9tRm9yZXN0KHg9cHJlZGljdG9ycyx5PXJlc3BvbnNlLHRyYWluaW5nX2ZyYW1lID0gbXVfZGF0YSwgbmZvbGRzPTEwLHNlZWQgPSAxMjM0KQpgYGAKCmBgYHtyIG1vZGVsIHBlcmZvcm1hbmNlLCBjYWNoZT1UUlVFfQoKdGVzdDJfbW90aGVyX211X2RhdGFfUkZtb2RlbApgYGAKCiAgICBBVUM6ICAwLjkzNDYwNDUKICAgIEFVQ1BSOiAgMC42MTU3NTczCgpUaGF0IHdhcyBhIHF1aWNrIHRlc3QuCgpOb3cgSSBuZWVkIHRvIGNyZWF0ZSBhIGhvbGQtb3V0IGRhdGFzZXQgZmlyc3Q7IHJlcGVhdCB0aGUgYWJvdmUgdGhlbiBhcyB0ZXN0OnZhbGlkYXRpb24uCgojIyBTcGxpdCB0aGUgZGF0YSAwLjggZm9yIHRyYWluaW5nOnZhbGlkYXRpb24KCmBgYHtyIGRhdGFfc3BsaXQsIGNhY2hlPVRSVUV9Cm1vdGhlcjJfbXVfZGF0YV9zcGxpdCA8LSBoMm8uc3BsaXRGcmFtZShtdV9kYXRhLCByYXRpb3M9MC44LCBzZWVkID0gMSkKYGBgCgpgYGB7ciBhc3NpZ25fdHJhaW5fdmFsaWRhdGlvbiwgY2FjaGU9VFJVRX0KdHJhaW49bW90aGVyMl9tdV9kYXRhX3NwbGl0W1sxXV0KdmFsaWQ9bW90aGVyMl9tdV9kYXRhX3NwbGl0W1syXV0KYGBgCgojIyBUcmFpbiB0aGUgRFJGIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBkYXRhc2V0ID0gMC44CgpgYGB7ciB0cmFpbl9EUkZfb25fdGVzdF9kYXRhc2V0LCBjYWNoZT1UUlVFfQpwdG08LXByb2MudGltZSgpCm1vdGhlcjJfbXVfZGF0YV9SRm1vZGVsPC1oMm8ucmFuZG9tRm9yZXN0KHg9cHJlZGljdG9ycyx5PXJlc3BvbnNlLHRyYWluaW5nX2ZyYW1lID0gdHJhaW4sIG5mb2xkcz0xMCxzZWVkID0gMTIzNCkKcHJvYy50aW1lKCktcHRtCgpgYGAKCiMjIFByZWRpY3QgdXNpbmcgdGhlIERSRiBtb2RlbCBvbiB0aGUgdGVzdGluZyBkYXRhc2V0ID0wLjIKCj4gWWllbGRzIEFVQzogIDAuOTI4ODM4MyBhbmQgQVVDUFI6ICAwLjYzMzg4MTcKCmBgYHtyIHBlcmZvcm1hbmNlIG9mIHRoZSBfcmVhbF8gRFJGIG1vZGVsLCBtZXNzYWdlPVRSVUUsIHBhZ2VkLnByaW50PVRSVUUsIGNhY2hlPVRSVUV9CnB0bTwtcHJvYy50aW1lKCkKCm1vdGhlcjJfbXVfZGF0YV9wcmVkaWN0PC1oMm8ucHJlZGljdChvYmplY3Q9bW90aGVyMl9tdV9kYXRhX1JGbW9kZWwsIG5ld2RhdGE9dmFsaWQpCgptb3RoZXIyX211X2RhdGFfUkZtb2RlbAoKcHJvYy50aW1lKCktcHRtCmBgYAoKIyMjIEFVUk9DcHIgZm9yIHByZWRpY3RpbmcgcmUtaG9zcGl0YWxpemF0aW9uIGJhc2VkIG9uIHBhdGllbnQgZGVtb2dyYXBoaWNzIGFsb25lCgpgYGB7ciBwbG90IEFVUk9DcHIsIGVjaG89VFJVRSwgY2FjaGU9VFJVRX0KCm1vZD1tb3RoZXIyX211X2RhdGFfUkZtb2RlbAoKcGVyZiA8LSBoMm8ucGVyZm9ybWFuY2UobW9kLHZhbGlkKQoKbWV0cmljcyA8LSBhcy5kYXRhLmZyYW1lKGgyby5tZXRyaWMocGVyZikpCgptZXRyaWNzCgptZXRyaWNzICU+JQogIGdncGxvdChhZXMocmVjYWxsLHByZWNpc2lvbikpICsgCiAgZ2VvbV9saW5lKCkgKwogIHRoZW1lX21pbmltYWwoKQoKbWV0cmljcyAlPiUKICBnZ3Bsb3QoYWVzKHByZWNpc2lvbiwgYWNjdXJhY3kpKSArIAogIGdlb21fbGluZSgpICsKICB0aGVtZV9taW5pbWFsKCkKCmBgYAoKIyMjIEV4cGxhaW4gdGhlIG1vZGVsCgpgYGB7ciBtb2RlbF9wZXJmb3JtYW5jZV9vbl90ZXN0X2RhdGFzZXQsIGNhY2hlPVRSVUV9CgpwdG08LXByb2MudGltZSgpCgojIHRvZ2dsZSBwcm9ncmVzcyBiYXIgaWYgZGVzaXJlZDoKIyBoMm8uc2hvd19wcm9ncmVzcygpIAoKZXhwIDwtaDJvLmV4cGxhaW4ob2JqZWN0PW1vdGhlcjJfbXVfZGF0YV9SRm1vZGVsLCBuZXdkYXRhPXZhbGlkKQpwcmludChleHApCgpwcm9jLnRpbWUoKS1wdG0KYGBgCgojIyMgU3RhdGlzdGljcyBzdW1tYXJ5CgpgYGB7ciBmdW5jdGlvbiB0byBzZWUgdGhlIHJlc3VsdHMsIGNhY2hlPVRSVUV9CgpyZXN1bHRzX2RmIDwtIGZ1bmN0aW9uKGgyb19tb2RlbCkgewogIGgyb19tb2RlbEBtb2RlbCRjcm9zc192YWxpZGF0aW9uX21ldHJpY3Nfc3VtbWFyeSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgc2VsZWN0KC1tZWFuLCAtc2QpICU+JSAKICAgIHQoKSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgbXV0YXRlX2FsbChhcy5jaGFyYWN0ZXIpICU+JSAKICAgIG11dGF0ZV9hbGwoYXMubnVtZXJpYykgLT4gawogIAogIGsgJT4lIAogICAgc2VsZWN0KEFjY3VyYWN5ID0gYWNjdXJhY3ksCiAgICAgICAgICAgQVVDID0gYXVjLAogICAgICAgICAgIFByZWNpc2lvbiA9IHByZWNpc2lvbiwKICAgICAgICAgICBTcGVjaWZpY2l0eSA9IHNwZWNpZmljaXR5LAogICAgICAgICAgIFJlY2FsbCA9IHJlY2FsbCwKICAgICAgICAgICBMb2dsb3NzID0gbG9nbG9zcykgJT4lIAogIHJldHVybigpCn0KYGBgCgpgYGB7ciBwcm9kdWNpbmcgdGhlIHN0YXRpc3RpY3Mgc3VtbWFyeSwgY2FjaGU9VFJVRX0KCiMgVXNpbmcgZnVuY3Rpb24KcmVzdWx0c19kZihtb2QpIC0+IG91dGNvbWUKCiMgT3V0Y29tZSAKb3V0Y29tZSAlPiUgCiAgZ2F0aGVyKE1ldHJpY3MsIFZhbHVlcykgJT4lIAogIGdncGxvdChhZXMoTWV0cmljcywgVmFsdWVzLCBmaWxsID0gTWV0cmljcywgY29sb3IgPSBNZXRyaWNzKSkgKwogIGdlb21fYm94cGxvdChhbHBoYSA9IDAuMywgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAKICBmYWNldF93cmFwKH4gTWV0cmljcywgc2NhbGVzID0gImZyZWUiKSArIAogIGxhYnModGl0bGUgPSAiUGVyZm9ybWFuY2Ugb2Ygb3VyIE1MIG1vZGVsIHVzaW5nIEgybyBwYWNrYWdlICIsCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBOSUNIRCBEZWNvZGluZyBNYXRlcm5hbCBNb3JiaWRpdHkgRGF0YSBDaGFsbGVuZ2VcbkNyZWF0ZWQgYnkgTWFydGluIEZyYXNjaCAoZnVydGhlciBjcmVkaXQgdG8gaHR0cHM6Ly9iaXQubHkvM0JwUHFjYikiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3IgVGFidWxhciBzdGF0aXN0aWNzIHN1bW1hcnksIGNhY2hlPVRSVUV9CgojIFN0YXRpc3RpY3Mgc3VtbWFyeQpvdXRjb21lICU+JSAKICBnYXRoZXIoTWV0cmljcywgVmFsdWVzKSAlPiUgCiAgZ3JvdXBfYnkoTWV0cmljcykgJT4lIAogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWVhbiwgbWVkaWFuLCBtaW4sIG1heCwgc2QsIG4oKSkpICU+JSAKICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3JvdW5kKDEwMCp4LCAyKX0pICU+JQogIGtuaXRyOjprYWJsZShjb2wubmFtZXMgPSBjKCJDcml0ZXJpb24iLCAiTWVhbiIsICJNZWRpYW4iLCAiTWluIiwgIk1heCIsICJTRCIsICJOIikpCgpgYGAKIyMgQnVpbGRpbmcgYW4gaW50ZXJwcmV0YWJsZSBkZWNpc2lvbiB0cmVlIG1vZGVsCgpgYGB7ciBkZWNpc2lvbl90cmVlX21vZGVsLCBjYWNoZT1UUlVFfQoKbWF0ZXJuYWxfZHRfbW9kZWw8LWgyby5nYm0oeD1wcmVkaWN0b3JzLHk9cmVzcG9uc2UsdHJhaW5pbmdfZnJhbWUgPSB0cmFpbiwgdmFsaWRhdGlvbl9mcmFtZSA9IHZhbGlkLCBiYWxhbmNlX2NsYXNzZXMgPSBUUlVFLCBzZWVkID0gMTIzNCwgbmZvbGRzPTEwKQoKIyBHQk0gaHlwZXJwYXJhbXRlcnMKZ2JtX3BhcmFtcyA9IGxpc3QobWF4X2RlcHRoID0gc2VxKDIsIDEwKSkKCiMgVHJhaW4gYW5kIHZhbGlkYXRlIGEgY2FydGVzaWFuIGdyaWQgb2YgR0JNcwpnYm1fZ3JpZCA9IGgyby5ncmlkKCJnYm0iLCB4ID0gcHJlZGljdG9ycywgeSA9IHJlc3BvbnNlLAogICAgICAgICAgICAgICAgICAgIGdyaWRfaWQgPSAiZ2JtX2dyaWRfMXRyZWU4IiwKICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluLAogICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB2YWxpZCwKICAgICAgICAgICAgICAgICAgICBiYWxhbmNlX2NsYXNzZXMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDEsIG1pbl9yb3dzID0gMSwgc2FtcGxlX3JhdGUgPSAxLCBjb2xfc2FtcGxlX3JhdGUgPSAxLAogICAgICAgICAgICAgICAgICAgIGxlYXJuX3JhdGUgPSAuMDEsIHNlZWQgPSAxMjM0LCAKICAgICAgICAgICAgICAgICAgICBoeXBlcl9wYXJhbXMgPSBnYm1fcGFyYW1zKQoKZ2JtX2dyaWRwZXJmID0gaDJvLmdldEdyaWQoZ3JpZF9pZCA9ICJnYm1fZ3JpZF8xdHJlZTgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzb3J0X2J5ID0gImF1YyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlY3JlYXNpbmcgPSBUUlVFKQoKYGBgCgpgYGB7ciBHQk1fcGVyZm9ybWFuY2UsIGNhY2hlPVRSVUV9CgojIHdoYXQgaXMgdGhlIHBlcmZvcm1hbmNlIG9mIHRoaXMgR0JNPwptYXRlcm5hbF9kdF9tb2RlbApgYGAKCldlIG9idGFpbiBBVUM6ICAwLjk0NzMwNjcsIEFVQ1BSOiAgMC42NTEwNTUzCgpgYGB7ciBHQk1fZ3JpZF9wZXJmb3JtYW5jZSwgY2FjaGU9VFJVRX0KZ2JtX2dyaWRwZXJmCmBgYAoKSW5mbGVjdGlvbiBwb2ludCBpcyBhdCBtYXhfZGVwdGg9NQoKYGBge3IgdHJhaW4gR0JNLCBjYWNoZT1UUlVFfQoKbWF0ZXJuYWxfMV90cmVlID0gaDJvLmdibSh4ID0gcHJlZGljdG9ycywgeSA9IHJlc3BvbnNlLCAKICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbiwgYmFsYW5jZV9jbGFzc2VzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMSwgbWluX3Jvd3MgPSAxLCBzYW1wbGVfcmF0ZSA9IDEsIGNvbF9zYW1wbGVfcmF0ZSA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgIG1heF9kZXB0aCA9IDUsCiAgICAgICAgICAgICAgICAgICMgdXNlIGVhcmx5IHN0b3BwaW5nIG9uY2UgdGhlIHZhbGlkYXRpb24gQVVDIGRvZXNuJ3QgaW1wcm92ZSBieSBhdCBsZWFzdCAwLjAxJQogICAgICAgICAgICAgICAgICAjIGZvciA1IGNvbnNlY3V0aXZlIHNjb3JpbmcgZXZlbnRzCiAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDMsIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAuMDEsIAogICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAiQVVDIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAxKQptYXRlcm5hbF8xX3RyZWUKCgpgYGAKCiAgICBBVUNQUjogIDAuODgyNzYyCgpgYGB7ciBtYXRlcm5hbF90cmVlLCBjYWNoZT1UUlVFfQptYXRlcm5hbF9UcmVlID0gaDJvLmdldE1vZGVsVHJlZShtb2RlbCA9IG1hdGVybmFsXzFfdHJlZSwgdHJlZV9udW1iZXIgPSAxKQpgYGAKCmBgYHtyIGZ1bmN0aW9uX3Zpel90cmVlLCBjYWNoZT1UUlVFfQoKIyBWaXN1YWxpemluZyBIMk8gVHJlZXMKCmxpYnJhcnkoZGF0YS50cmVlKQoKY3JlYXRlRGF0YVRyZWUgPC0gZnVuY3Rpb24oaDJvVHJlZSkgewogIAogIGgyb1RyZWVSb290ID0gaDJvVHJlZUByb290X25vZGUKICAKICBkYXRhVHJlZSA9IE5vZGUkbmV3KGgyb1RyZWVSb290QHNwbGl0X2ZlYXR1cmUpCiAgZGF0YVRyZWUkdHlwZSA9ICdzcGxpdCcKICAKICBhZGRDaGlsZHJlbihkYXRhVHJlZSwgaDJvVHJlZVJvb3QpCiAgCiAgcmV0dXJuKGRhdGFUcmVlKQp9CgphZGRDaGlsZHJlbiA8LSBmdW5jdGlvbihkdHJlZSwgbm9kZSkgewogIAogIGlmKGNsYXNzKG5vZGUpWzFdICE9ICdIMk9TcGxpdE5vZGUnKSByZXR1cm4oVFJVRSkKICAKICBmZWF0dXJlID0gbm9kZUBzcGxpdF9mZWF0dXJlCiAgaWQgPSBub2RlQGlkCiAgbmFfZGlyZWN0aW9uID0gbm9kZUBuYV9kaXJlY3Rpb24KICAKICBpZihpcy5uYShub2RlQHRocmVzaG9sZCkpIHsKICAgIGxlZnRFZGdlTGFiZWwgPSBwcmludFZhbHVlcyhub2RlQGxlZnRfbGV2ZWxzLCBuYV9kaXJlY3Rpb249PSdMRUZUJywgNCkKICAgIHJpZ2h0RWRnZUxhYmVsID0gcHJpbnRWYWx1ZXMobm9kZUByaWdodF9sZXZlbHMsIG5hX2RpcmVjdGlvbj09J1JJR0hUJywgNCkKICB9ZWxzZSB7CiAgICBsZWZ0RWRnZUxhYmVsID0gcGFzdGUoIjwiLCBub2RlQHRocmVzaG9sZCwgaWZlbHNlKG5hX2RpcmVjdGlvbj09J0xFRlQnLCcsTkEnLCcnKSkKICAgIHJpZ2h0RWRnZUxhYmVsID0gcGFzdGUoIj49Iiwgbm9kZUB0aHJlc2hvbGQsIGlmZWxzZShuYV9kaXJlY3Rpb249PSdSSUdIVCcsJyxOQScsJycpKQogIH0KICAKICBsZWZ0X25vZGUgPSBub2RlQGxlZnRfY2hpbGQKICByaWdodF9ub2RlID0gbm9kZUByaWdodF9jaGlsZAogIAogIGlmKGNsYXNzKGxlZnRfbm9kZSlbWzFdXSA9PSAnSDJPTGVhZk5vZGUnKQogICAgbGVmdExhYmVsID0gcGFzdGUoInByZWRpY3Rpb246IiwgbGVmdF9ub2RlQHByZWRpY3Rpb24pCiAgZWxzZQogICAgbGVmdExhYmVsID0gbGVmdF9ub2RlQHNwbGl0X2ZlYXR1cmUKICAKICBpZihjbGFzcyhyaWdodF9ub2RlKVtbMV1dID09ICdIMk9MZWFmTm9kZScpCiAgICByaWdodExhYmVsID0gcGFzdGUoInByZWRpY3Rpb246IiwgcmlnaHRfbm9kZUBwcmVkaWN0aW9uKQogIGVsc2UKICAgIHJpZ2h0TGFiZWwgPSByaWdodF9ub2RlQHNwbGl0X2ZlYXR1cmUKICAKICBpZihsZWZ0TGFiZWwgPT0gcmlnaHRMYWJlbCkgewogICAgbGVmdExhYmVsID0gcGFzdGUobGVmdExhYmVsLCAiKEwpIikKICAgIHJpZ2h0TGFiZWwgPSBwYXN0ZShyaWdodExhYmVsLCAiKFIpIikKICB9CiAgCiAgZHRyZWVMZWZ0ID0gZHRyZWUkQWRkQ2hpbGQobGVmdExhYmVsKQogIGR0cmVlTGVmdCRlZGdlTGFiZWwgPSBsZWZ0RWRnZUxhYmVsCiAgZHRyZWVMZWZ0JHR5cGUgPSBpZmVsc2UoY2xhc3MobGVmdF9ub2RlKVsxXSA9PSAnSDJPU3BsaXROb2RlJywgJ3NwbGl0JywgJ2xlYWYnKQogIAogIGR0cmVlUmlnaHQgPSBkdHJlZSRBZGRDaGlsZChyaWdodExhYmVsKQogIGR0cmVlUmlnaHQkZWRnZUxhYmVsID0gcmlnaHRFZGdlTGFiZWwKICBkdHJlZVJpZ2h0JHR5cGUgPSBpZmVsc2UoY2xhc3MocmlnaHRfbm9kZSlbMV0gPT0gJ0gyT1NwbGl0Tm9kZScsICdzcGxpdCcsICdsZWFmJykKICAKICBhZGRDaGlsZHJlbihkdHJlZUxlZnQsIGxlZnRfbm9kZSkKICBhZGRDaGlsZHJlbihkdHJlZVJpZ2h0LCByaWdodF9ub2RlKQogIAogIHJldHVybihGQUxTRSkKfQoKcHJpbnRWYWx1ZXMgPC0gZnVuY3Rpb24odmFsdWVzLCBpc19uYV9kaXJlY3Rpb24sIG49NCkgewogIGwgPSBsZW5ndGgodmFsdWVzKQogIAogIGlmKGwgPT0gMCkKICAgIHZhbHVlX3N0cmluZyA9IGlmZWxzZShpc19uYV9kaXJlY3Rpb24sICJOQSIsICIiKQogIGVsc2UKICAgIHZhbHVlX3N0cmluZyA9IHBhc3RlMChwYXN0ZTAodmFsdWVzWzE6bWluKG4sbCldLCBjb2xsYXBzZSA9ICcsICcpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShsID4gbiwgIiwuLi4iLCAiIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGlzX25hX2RpcmVjdGlvbiwgIiwgTkEiLCAiIikpCiAgCiAgcmV0dXJuKHZhbHVlX3N0cmluZykKfQoKCmBgYAoKVGhpcyBkZWNpc2lvbiB0cmVlLCBhbHNvIHN1cHBsaWVkIGFzIFBERiwgaXMgbWVhbnQgdG8gaGVscCBidWlsZCBpbnR1aXRpb24gYWJvdXQgaG93IHRoZSBtb2RlbC4KCmBgYHtyIGRlY2lzaW9uX3RyZWVfaDJvLCBjYWNoZT1UUlVFfQoKbGlicmFyeShEaWFncmFtbWVSKQoKIyBjdXN0b21pemVkIERUIGZvciBvdXIgSDJPIG1vZGVsCgptYXRlcm5hbF9tdTJEYXRhVHJlZSA9IGNyZWF0ZURhdGFUcmVlKG1hdGVybmFsX1RyZWUpCgpHZXRFZGdlTGFiZWwgPC0gZnVuY3Rpb24obm9kZSkge3JldHVybiAobm9kZSRlZGdlTGFiZWwpfQpHZXROb2RlU2hhcGUgPC0gZnVuY3Rpb24obm9kZSkge3N3aXRjaChub2RlJHR5cGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9ICJkaWFtb25kIiwgbGVhZiA9ICJvdmFsIil9CkdldEZvbnROYW1lIDwtIGZ1bmN0aW9uKG5vZGUpIHtzd2l0Y2gobm9kZSR0eXBlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9ICdQYWxhdGluby1ib2xkJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVhZiA9ICdQYWxhdGlubycpfQpTZXRFZGdlU3R5bGUobWF0ZXJuYWxfbXUyRGF0YVRyZWUsIGZvbnRuYW1lID0gJ1BhbGF0aW5vLWl0YWxpYycsIAogICAgICAgICAgICAgbGFiZWwgPSBHZXRFZGdlTGFiZWwsIGxhYmVsZmxvYXQgPSBUUlVFLAogICAgICAgICAgICAgZm9udHNpemUgPSAiMjYiLCBmb250Y29sb3I9J3JveWFsYmx1ZTQnKQpTZXROb2RlU3R5bGUobWF0ZXJuYWxfbXUyRGF0YVRyZWUsIGZvbnRuYW1lID0gR2V0Rm9udE5hbWUsIHNoYXBlID0gR2V0Tm9kZVNoYXBlLCAKICAgICAgICAgICAgIGZvbnRzaXplID0gIjI2IiwgZm9udGNvbG9yPSdyb3lhbGJsdWU0JywKICAgICAgICAgICAgIGhlaWdodD0iMC43NSIsIHdpZHRoPSIxIikKClNldEdyYXBoU3R5bGUobWF0ZXJuYWxfbXUyRGF0YVRyZWUsIHJhbmtkaXIgPSAiTFIiLCBkcGk9NzAuKQoKcGxvdChtYXRlcm5hbF9tdTJEYXRhVHJlZSwgb3V0cHV0ID0gImdyYXBoIikKCmBgYAoKYGBge3IgZ2VuZXJhdGUgaDJvIGV4cGxhbmF0aW9uIG9mIHRoZSBEVCBtb2RlbCwgY2FjaGU9VFJVRX0KcHRtPC1wcm9jLnRpbWUoKQoKZXhwX2R0PC1oMm8uZXhwbGFpbihtYXRlcm5hbF9kdF9tb2RlbCx2YWxpZCkKCnByb2MudGltZSgpLXB0bQpgYGAKCmBgYHtyIHNob3cgdGhlIGV4cGxhbmF0aW9uLCBjYWNoZT1UUlVFfQoKZXhwX2R0CmBgYAoKIyMgVXNpbmcgKipOYcOvdmUgQmF5ZXMgQ2xhc3NpZmllcioqCgpgYGB7ciBOYcOvdmUgQmF5ZXMgQ2xhc3NpZmllciwgY2FjaGU9VFJVRX0KCiMgQnVpbGQgYW5kIHRyYWluIHRoZSBtb2RlbDoKbW8yYl9uYiA8LSBoMm8ubmFpdmVCYXllcyh4ID0gcHJlZGljdG9ycywKICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gcmVzcG9uc2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICBsYXBsYWNlID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBuZm9sZHMgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTIzNCkKCiMgRXZhbCBwZXJmb3JtYW5jZToKcGVyZiA8LSBoMm8ucGVyZm9ybWFuY2UobW8yYl9uYikKCiMgR2VuZXJhdGUgdGhlIHByZWRpY3Rpb25zIG9uIGEgdGVzdCBzZXQgKGlmIG5lY2Vzc2FyeSk6CnByZWQgPC0gaDJvLnByZWRpY3QobW8yYl9uYiwgbmV3ZGF0YSA9IHZhbGlkKQpgYGAKCmBgYHtyIGJheWVzX3BlcmZvcm1hbmNlLCBjYWNoZT1UUlVFfQpwZXJmCmBgYApOQiBtb2RlbCBhY2hpZXZlcyBBVUM6ICAwLjkyNTkzOTMgYW5kIEFVQ1BSOiAgMC41MTIxNzA1CmBgYHtyIGV4cGxhaW4gbmFpdmUgYmF5ZXMgbW9kZWwsIGNhY2hlPVRSVUV9CgojIGJlc3Qgdmlld2VkIGluIGEgbmV3IHdpbmRvdyBvciBzZWUsIHBsZWFzZSwgdGhlIFBERiBpbmNsdWRlZCB3aXRoIHRoZSBzdWJtaXNzaW9uCmV4cF9uYiA8LSBoMm8uZXhwbGFpbihtbzJiX25iLHZhbGlkKQpgYGAKCk5vdGUgdGhlIGhpZ2hseSB2YXJpYWJsZSBwYXJ0aWFsIGltcG9ydGFuY2Ugb2YgdGhlIGRpZmZlcmVudCBzb2Npby1kZW1vZ3JhcGhpYyBjaGFyYWN0ZXJpc3RpY3MKCmBgYHtyIHZ1el9uYl9wZXJmb3JtYW5jZSwgY2FjaGU9VFJVRX0KCmV4cF9uYgpgYGAKCiMjIEV4dGVuZGluZyB0aGUgTUwgdG9vbGJveDogVXNpbmcgaDJvIEF1dG9NTCBtb2RlIHRvIGZpbmQgYW4gb2JqZWN0aXZlbHkgYmVzdCBwZXJmb3JtaW5nIG1vZGVsCgpDb21wYXJlIFtoZXJlXShodHRwOi8vaDJvLXJlbGVhc2UuczMuYW1hem9uYXdzLmNvbS9oMm8vcmVsLXlhdGVzLzEvZG9jcy13ZWJzaXRlL2gyby1kb2NzL2F1dG9tbC5odG1sI2NvZGUtZXhhbXBsZXMpLiBUaGUgZmluZGluZ3MgYXJlIHRvIGJlIGludGVycHJldGVkIHdpdGggY2F1dGlvbiBhdCB0aGlzIHN0YWdlLiBPbmNlIHdlIG9idGFpbiBhIGxhcmdlciBleHRlcm5hbCBkYXRhc2V0IGZvciB2YWxpZGF0aW9uLCB3aXRoIGEgbW9yZSBiYWxhbmNlZCBjYXNlIGRpc3RyaWJ1dGlvbiwgdGhpcyB3aWxsIGJlY29tZSBtb3JlIHVzZWZ1bCBhbmQgYWxsb3cgYnVpbGRpbmcgYW4gaW5mZXJlbmNlIGVuZ2luZSB0aGF0IGNvdWxkIGJlIGRlcGxveWVkIGZvciB1c2UuIEkgYW0gcHJlc2VudGluZyB0aGlzIGNvZGUgdGhlcmVmb3JlIGFzIGEgcmVmZXJlbmNlIGZvciBmdXR1cmUgd29yay4KCk5ldmVydGhlbGVzcywgaXQgaXMgZXZpZGVudCB0aGF0IGFuIG9wdGltaXphdGlvbiBldmVuIGF0IHRoaXMgc3RhZ2UgcmVzdWx0cyBpbiBhIGNsYXNzaWZpY2F0aW9uIHByZWRpY3Rpb24gcGVyZm9ybWFuY2Ugb2YgQVVST0MgPSAwLjk5MzczNDkuIFRoaXMgcmVzdWx0IGNhbiB2YXJ5IGRlcGVuZGluZyBvbiB0aGUgcnVuLgoKTm90ZSBwbGVhc2UsIHRoaXMgY29kZSBydW5zIGZvciBhYm91dCA3MCBtaW4gb24gYSB3ZWxsLWVxdWlwcGVkIGRlZXAgbGVhcm5pbmcgd29ya3N0YXRpb24uCgojIyMgVHJhaW4gaW4gQXV0b01MIG1vZGUKCmBgYHtyIGF1dG9fbWwsIGNhY2hlPVRSVUV9CgpwdG08LXByb2MudGltZSgpCgptYXRlcm5hbF9hbWwgPC0gaDJvLmF1dG9tbCh4PXByZWRpY3RvcnMseT1yZXNwb25zZSx0cmFpbmluZ19mcmFtZSA9IHRyYWluLCBtYXhfbW9kZWxzID0gMjAsIHNlZWQgPSAxKQoKbWF0ZXJuYWxfbGIgPC0gbWF0ZXJuYWxfYW1sQGxlYWRlcmJvYXJkCgojcHJpbnQobWF0ZXJuYWxfbGIsIG4gPSBucm93KG1hdGVybmFsX2xiKSkgI1ByaW50IGFsbCByb3dzIGluc3RlYWQgb2YgZGVmYXVsdCA2IHJvd3MKCnByb2MudGltZSgpLXB0bQpgYGAKCiMjIyBWYWxpZGF0ZSB0aGUgY29tYmluZWQgbGVhZGVyIGFtbCBtb2RlbAoKYGBge3IgdmFsaWRhdGVfYW1sLCBjYWNoZT1UUlVFfQpwdG08LXByb2MudGltZSgpCgptYXRlcm5hbF9wZXJmX3ZhbGlkIDwtIGgyby5wZXJmb3JtYW5jZShtYXRlcm5hbF9hbWxAbGVhZGVyLG5ld2RhdGE9dmFsaWQseHZhbD1GQUxTRSx2YWxpZD1UUlVFKQoKcHJlZCA8LSBoMm8ucHJlZGljdChtYXRlcm5hbF9hbWxAbGVhZGVyLCB2YWxpZCkKYGBgCgpgYGB7cn0KaDJvLmF1YyhtYXRlcm5hbF9hbWxAbGVhZGVyLCBjYWNoZT1UUlVFKQpgYGAKCltgRXhwbGFpbiB0aGUgbW9kZWxgXShodHRwczovL2RvY3MuaDJvLmFpL2gyby9sYXRlc3Qtc3RhYmxlL2gyby1kb2NzL2V4cGxhaW4uaHRtbCkKCldlIG9ic2VydmUgbm8gc3BlY2lmaWNpdHkgYmVjYXVzZSB0aGUgZGF0YXNldCBpcyB1bmJhbGFuY2VkIHN1Y2ggdGhhdCBieSBsdWNrIG9mIGRyYXcgKHdoZW4gdGhlIGRhdGFzZXQgaXMgc3BsaXQgODA6MjApIHdlIGdldCBubyB0cnVlIHBvc2l0aXZlcy4KCmBgYHtyIGV4cGxhaW5fYmVzdF9tb2RlbCwgY2FjaGU9VFJVRX0KcHRtPC1wcm9jLnRpbWUoKQpleHAgPC1oMm8uZXhwbGFpbihtYXRlcm5hbF9hbWxAbGVhZGVyLCB2YWxpZCkKcHJvYy50aW1lKCktcHRtCmBgYAoKYGBge3IsIGNhY2hlPVRSVUV9CnByaW50KGV4cCkKCmBgYAoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==