This is an exemplar workbook showing how the Vaar Notebook can be applied to UCI’s Cervical Cancer (Risk Factors) dataset (Fernandes et al., 2017) to return:
- Whether data is more likely to be MCAR or MAR/MNAR
- A recommended approach for managing missing data that considers stability of model results
- Evaluation of imputation efforts based on data model experiment results

Step 1: Read in data and show data summary

Data.table package used to read in file from website.(Fernandes et al., 2017)

library(data.table)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
data.table 1.14.6 using 10 threads (see ?getDTthreads).  Latest news: r-datatable.com
df <- fread('https://archive.ics.uci.edu/ml/machine-learning-databases/00383/risk_factors_cervical_cancer.csv', header=TRUE, stringsAsFactors = FALSE)

 Downloaded 4021 bytes...
 Downloaded 8117 bytes...
 Downloaded 12213 bytes...
 Downloaded 16309 bytes...
 Downloaded 28597 bytes...
 Downloaded 40885 bytes...
 Downloaded 44981 bytes...
 Downloaded 53173 bytes...
 Downloaded 65461 bytes...
 Downloaded 77749 bytes...
 Downloaded 94133 bytes...
 Downloaded 98229 bytes...
 Downloaded 102059 bytes...
head(df)
summary(df)
      Age        Number of sexual partners First sexual intercourse Num of pregnancies    Smokes          Smokes (years)    
 Min.   :13.00   Length:858                Length:858               Length:858         Length:858         Length:858        
 1st Qu.:20.00   Class :character          Class :character         Class :character   Class :character   Class :character  
 Median :25.00   Mode  :character          Mode  :character         Mode  :character   Mode  :character   Mode  :character  
 Mean   :26.82                                                                                                              
 3rd Qu.:32.00                                                                                                              
 Max.   :84.00                                                                                                              
 Smokes (packs/year) Hormonal Contraceptives Hormonal Contraceptives (years)     IUD            IUD (years)       
 Length:858          Length:858              Length:858                      Length:858         Length:858        
 Class :character    Class :character        Class :character                Class :character   Class :character  
 Mode  :character    Mode  :character        Mode  :character                Mode  :character   Mode  :character  
                                                                                                                  
                                                                                                                  
                                                                                                                  
     STDs           STDs (number)      STDs:condylomatosis STDs:cervical condylomatosis STDs:vaginal condylomatosis
 Length:858         Length:858         Length:858          Length:858                   Length:858                 
 Class :character   Class :character   Class :character    Class :character             Class :character           
 Mode  :character   Mode  :character   Mode  :character    Mode  :character             Mode  :character           
                                                                                                                   
                                                                                                                   
                                                                                                                   
 STDs:vulvo-perineal condylomatosis STDs:syphilis      STDs:pelvic inflammatory disease STDs:genital herpes
 Length:858                         Length:858         Length:858                       Length:858         
 Class :character                   Class :character   Class :character                 Class :character   
 Mode  :character                   Mode  :character   Mode  :character                 Mode  :character   
                                                                                                           
                                                                                                           
                                                                                                           
 STDs:molluscum contagiosum  STDs:AIDS           STDs:HIV         STDs:Hepatitis B     STDs:HPV        
 Length:858                 Length:858         Length:858         Length:858         Length:858        
 Class :character           Class :character   Class :character   Class :character   Class :character  
 Mode  :character           Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                                       
                                                                                                       
                                                                                                       
 STDs: Number of diagnosis STDs: Time since first diagnosis STDs: Time since last diagnosis   Dx:Cancer           Dx:CIN       
 Min.   :0.00000           Length:858                       Length:858                      Min.   :0.00000   Min.   :0.00000  
 1st Qu.:0.00000           Class :character                 Class :character                1st Qu.:0.00000   1st Qu.:0.00000  
 Median :0.00000           Mode  :character                 Mode  :character                Median :0.00000   Median :0.00000  
 Mean   :0.08741                                                                            Mean   :0.02098   Mean   :0.01049  
 3rd Qu.:0.00000                                                                            3rd Qu.:0.00000   3rd Qu.:0.00000  
 Max.   :3.00000                                                                            Max.   :1.00000   Max.   :1.00000  
     Dx:HPV              Dx            Hinselmann         Schiller          Citology           Biopsy      
 Min.   :0.00000   Min.   :0.00000   Min.   :0.00000   Min.   :0.00000   Min.   :0.00000   Min.   :0.0000  
 1st Qu.:0.00000   1st Qu.:0.00000   1st Qu.:0.00000   1st Qu.:0.00000   1st Qu.:0.00000   1st Qu.:0.0000  
 Median :0.00000   Median :0.00000   Median :0.00000   Median :0.00000   Median :0.00000   Median :0.0000  
 Mean   :0.02098   Mean   :0.02797   Mean   :0.04079   Mean   :0.08625   Mean   :0.05128   Mean   :0.0641  
 3rd Qu.:0.00000   3rd Qu.:0.00000   3rd Qu.:0.00000   3rd Qu.:0.00000   3rd Qu.:0.00000   3rd Qu.:0.0000  
 Max.   :1.00000   Max.   :1.00000   Max.   :1.00000   Max.   :1.00000   Max.   :1.00000   Max.   :1.0000  

Lots of variables imported as characters as there is missing data and lots of boolean variables also.

str(df)
Classes ‘data.table’ and 'data.frame':  858 obs. of  36 variables:
 $ Age                               : int  18 15 34 52 46 42 51 26 45 44 ...
 $ Number of sexual partners         : chr  "4.0" "1.0" "1.0" "5.0" ...
 $ First sexual intercourse          : chr  "15.0" "14.0" "?" "16.0" ...
 $ Num of pregnancies                : chr  "1.0" "1.0" "1.0" "4.0" ...
 $ Smokes                            : chr  "0.0" "0.0" "0.0" "1.0" ...
 $ Smokes (years)                    : chr  "0.0" "0.0" "0.0" "37.0" ...
 $ Smokes (packs/year)               : chr  "0.0" "0.0" "0.0" "37.0" ...
 $ Hormonal Contraceptives           : chr  "0.0" "0.0" "0.0" "1.0" ...
 $ Hormonal Contraceptives (years)   : chr  "0.0" "0.0" "0.0" "3.0" ...
 $ IUD                               : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ IUD (years)                       : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs                              : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs (number)                     : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:condylomatosis               : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:cervical condylomatosis      : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:vaginal condylomatosis       : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:vulvo-perineal condylomatosis: chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:syphilis                     : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:pelvic inflammatory disease  : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:genital herpes               : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:molluscum contagiosum        : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:AIDS                         : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:HIV                          : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:Hepatitis B                  : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs:HPV                          : chr  "0.0" "0.0" "0.0" "0.0" ...
 $ STDs: Number of diagnosis         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ STDs: Time since first diagnosis  : chr  "?" "?" "?" "?" ...
 $ STDs: Time since last diagnosis   : chr  "?" "?" "?" "?" ...
 $ Dx:Cancer                         : int  0 0 0 1 0 0 0 0 1 0 ...
 $ Dx:CIN                            : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Dx:HPV                            : int  0 0 0 1 0 0 0 0 1 0 ...
 $ Dx                                : int  0 0 0 0 0 0 0 0 1 0 ...
 $ Hinselmann                        : int  0 0 0 0 0 0 1 0 0 0 ...
 $ Schiller                          : int  0 0 0 0 0 0 1 0 0 0 ...
 $ Citology                          : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Biopsy                            : int  0 0 0 0 0 0 1 0 0 0 ...
 - attr(*, ".internal.selfref")=<externalptr> 

Step 2: Copy and Tidy Data

library(tidyverse)
library(janitor)
library(naniar)
#clean variable names
df <- df %>%
        clean_names()
dfcp <- df #copy data

#assign value NA to "?"
dfcp <- dfcp %>% replace_with_na_all(condition = ~.x == "?")

Remove variables with no variance

Summary identified that st_ds_cervical_condylomatosis and st_ds_aids have no variance. Removing them from dataframe as there are lots of variables in this data and these will not add value to the model.

dfcp <- select(dfcp,-c(st_ds_aids))
dfcp <- select(dfcp,-c(st_ds_cervical_condylomatosis))

Check for Duplicates

Duplicates checked using n_distinct in tidyverse package

n_distinct(dfcp)
[1] 835
#filter to check and review any duplicate records
duplicates <- dfcp %>%
  filter(duplicated(.))

print(duplicates)

Remove duplicates and any unnecessary variables for model such as id

There are no unique identifiers, so these may not be duplicates and will not be removed.

Step 3: Visualise Missing Data and run MCAR Test

Packages used: visdat to explore missing values, ggplot to explore missing data further and naniar for geom_miss_point function and MCAR test.
There will be a warning with MCAR test if non-numeric columns are present. If p-value >0.05 likely to be MCAR as not statistically significant. The naniar test may also error due to singular data and a fix has not been found for this yet (August 2023). However, the data owners (Fernandes et al., 2017) advise that several patients did not answer questions due to privacy concerns indicating that some of the data is likely to be MNAR.

There are four target variables in this dataset: Hinselmann, Schiller, Cytology and Biopsy.(Sindiani AM et al, 2020) investigated the risk factors associated with cervical cancer to study their possible association with the decision to take a cervical biopsy. The data problem to be tested in this notebook is the ability to accurately classify the decision to take a cervical biopsy.

library(visdat)
library(ggplot2)

vis_miss(dfcp)
Warning: `gather_()` was deprecated in tidyr 1.2.0.
Please use `gather()` instead.

miss_var_summary(dfcp)

#mcar_test(dfcp) # computationally singular

Step 4: Look at Outputs from Visualising the Missing Data and Answer the Following Questions

What is the p.value returned by the MCAR Test?

If the statistic is high and the p.value <0.05 it is likely to be MNAR or MAR and cannot be ignored. A p-value >0.05 indicates MCAR but other information will be considered also. The value is set as a variable in the following code. As the MCAR Test errored due to data singularity, “error” has been entered here.

#set MCAR Test p.value as variable
mcar_presult = "error"

if(mcar_presult=="error" || is.numeric(mcar_presult)){print(paste(mcar_presult, "is MCAR Test p.value variable value."))
}else{
    print("Please enter either a number or 'error' as the mcar_presult variable value.")}
[1] "error is MCAR Test p.value variable value."

Is data more likely to be missing in some variables?

If so, this indicates that the data could be missing at random (MAR) or missing not at random (MNAR) and can therefore not be ignored. Yes or No is set as a variable in the following code.

#set likelihood variable
likelihood = "Yes"

if(likelihood=="Yes" || likelihood=="No") {print(paste(likelihood, "is variable value for likelihood."))
}else{
    print("Please enter either 'Yes' or 'No' as the variable value for likelihood.")}
[1] "Yes is variable value for likelihood."

Is data missing from the dependent variable only?

If so, this suggests that complete case analysis may be the best option. However, other factors need to be considered also. Yes or No is set as variable in the following code.

#set dependent missingness variable
dependent_only = "No"

if(dependent_only=="Yes" || dependent_only=="No") {print(paste(dependent_only, "is variable value for dependent_only."))
}else{
    print("Please enter either 'Yes' or 'No' as the variable value for dependent_only.")}
[1] "No is variable value for dependent_only."

What proportion of data is missing?

If it’s between 0.1-0.5 multiple imputation is likely to result in a more effective, unbiased model. The value is set as a variable in the following code in the format 0.000. This is taken from the missing data visualisation.

#set missingness variable value
missingness = 0.117

if(is.numeric(missingness)){print(paste(missingness, "is missingness variable value."))
}else{
    print("Please enter a number as the missingness variable value.")}
[1] "0.117 is missingness variable value."
It’s good practice to test different approaches and the Vaar Notebooks will consider these, as well as the variable values just set in recommendations.

Correlation

Correlation for numerical variables.There are a large number of variables and indications of co-correlation in the matrix, so feature selection will be important.

#subset numerics for correlation only
#this will remove dependent variables also
dfcp_cor <- select_if(dfcp, is.numeric)
cor(dfcp_cor)
                                   age st_ds_number_of_diagnosis   dx_cancer       dx_cin      dx_hpv           dx   hinselmann
age                        1.000000000              -0.001605942  0.11033968  0.061443423  0.10172170  0.092635125 -0.003966847
st_ds_number_of_diagnosis -0.001605942               1.000000000 -0.01542288  0.008069606 -0.01542288 -0.002288585  0.076786976
dx_cancer                  0.110339681              -0.015422882  1.00000000 -0.015071762  0.88650794  0.665647059  0.134263602
dx_cin                     0.061443423               0.008069606 -0.01507176  1.000000000 -0.01507176  0.606938678 -0.021232519
dx_hpv                     0.101721696              -0.015422882  0.88650794 -0.015071762  1.00000000  0.616327096  0.134263602
dx                         0.092635125              -0.002288585  0.66564706  0.606938678  0.61632710  1.000000000  0.072214849
hinselmann                -0.003966847               0.076786976  0.13426360 -0.021232519  0.13426360  0.072214849  1.000000000
schiller                   0.103282769               0.130872847  0.15781160  0.009119105  0.15781160  0.098952103  0.650249194
citology                  -0.016862074               0.055114464  0.11344608 -0.023937652  0.11344608  0.088739964  0.192467108
biopsy                     0.055955515               0.097448921  0.16090497  0.113172334  0.16090497  0.157606644  0.547416628
                             schiller    citology     biopsy
age                       0.103282769 -0.01686207 0.05595552
st_ds_number_of_diagnosis 0.130872847  0.05511446 0.09744892
dx_cancer                 0.157811599  0.11344608 0.16090497
dx_cin                    0.009119105 -0.02393765 0.11317233
dx_hpv                    0.157811599  0.11344608 0.16090497
dx                        0.098952103  0.08873996 0.15760664
hinselmann                0.650249194  0.19246711 0.54741663
schiller                  1.000000000  0.36148649 0.73320388
citology                  0.361486486  1.00000000 0.32746639
biopsy                    0.733203881  0.32746639 1.00000000

Calculate Skew

library(psych) #for skewness function
skew(dfcp_cor)

Step 5: which variables and values are important for predicting proportion of missingness?

rpart and rpart.plot packages are used to plot a simple classification tree.

library(rpart)
library(rpart.plot)

dfcp %>%
  add_prop_miss() %>%
  rpart(prop_miss_all ~ ., data=.) %>%
  prp(type=4, extra = 101, roundint=FALSE, prefix="Prop.Miss = ")

Which numerical variable with missing data is nearest the top of the tree?

Variable value for missingness influencer set in code below.

#set value for missingness influencer
miss_influencer="st_ds_number_of_diagnosis"
library(mice)

Attaching package: ‘mice’

The following object is masked from ‘package:stats’:

    filter

The following objects are masked from ‘package:base’:

    cbind, rbind
fxpt <- fluxplot(dfcp)

# Variables with higher outflux may be more powerful predictors. More meaningful where data is missing from more than one variable.

Change Columns to Factors Where Necessary

dfcp <- as.data.frame(dfcp) 

dfcp <- dfcp  %>% mutate(across(c("smokes", "hormonal_contraceptives", "iud", "st_ds", "st_ds_condylomatosis", "st_ds_vaginal_condylomatosis", "st_ds_vulvo_perineal_condylomatosis", "st_ds_syphilis", "st_ds_pelvic_inflammatory_disease", "st_ds_genital_herpes", "st_ds_molluscum_contagiosum", "st_ds_hiv", "st_ds_hepatitis_b", "st_ds_hpv", "dx_cancer", "dx_cin", "dx_hpv", "dx", "hinselmann", "schiller", "citology", "biopsy"), as.factor))

Change Columns to Integers Where Necessary

dfcp <- dfcp  %>% mutate(across(c("number_of_sexual_partners", "first_sexual_intercourse", "num_of_pregnancies", "smokes_years", "smokes_packs_year","hormonal_contraceptives_years", "iud_years", "st_ds_number","st_ds_time_since_first_diagnosis", "st_ds_time_since_last_diagnosis"), as.integer))

Step 6: Create Complete Case and Multiple Imputed Datasets

Create complete dataset by deleting records with missing values and run Multiple Imputation using MICE.

dfcp_cca <- dfcp[complete.cases(dfcp), ] 

init = mice(dfcp, maxit=10)
meth = init$method=c("pmm", "pmm", "pmm", "pmm", "logreg", "pmm", "pmm", "logreg", "pmm", "logreg", "pmm", "logreg", "pmm", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg", "pmm", "pmm", "pmm", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg", "logreg")
predM = init$predictorMatrix

#create imputed data
set.seed(123)
dfcp_imp = mice(dfcp, method=meth, predictorMatrix=predM, m=5, nnet.MaxNWts=3000)
#create dataset after imputation
dfcp_mi <- complete(dfcp_imp)

Warnings given, visualisations created to ensure complete data returned.

#check for missing data in MI and CCA dataset
library(visdat)
vis_miss(dfcp_mi)

vis_miss(dfcp_cca)

Step 7: Conduct MNAR Sensitivity Test

Use the variable with missing data which has the most influence on proportion of missingness, according to step 5 and look at the range of values in this variable. Generate imputations under delta adjustment to imitate deviations from MAR (Gink and Van Buuren, no date). The code uses 0 for MAR and a reasonable range based on the variable range as the delta values to test MNAR.


#Generate imputations under delta adjustment
delta <- c(0, +0.2, +0.4, +0.6, +0.8) #mean is 0.8, max is 3
imp.d <- vector("list", length(delta))
post <-dfcp_imp$post

for (i in 1:length(delta)) {
  d <- delta[i]
  cmd <- paste("imp[[j]][,i] <- imp[[j]][,i] +", d)
  post["st_ds_number_of_diagnosis"] <- cmd
  imp <- mice(dfcp, post = post, maxit = 5,
              seed = i, print=FALSE)
  imp.d[[i]] <- imp
}

Inspect Imputations

The first plot is based on no delta adjustment and the second plot is the highest adjustment. The Y axis scale is set to the same value, so that differences can be visualised more easily.

densityplot(imp.d[[1]],lwd=3, ylim=c(0,3)) 

densityplot(imp.d[[5]],lwd=3, ylim=c(0,3)) 

Find out more about sensitivity analysis in the context of missing data from Gerko Vink and Stef van Buuren’s vignette mice: An approach to sensitivity analysis (Vink and van Buuren, no date).

The second density plot considers imputations under the largest adjustment, looking at the blue line in particular there does seem to be some change.

Create Complete Datasets with Delta Imputations 1 to 5

Create complete datasets and check imputations have worked by visualising missing data.

#delta 1 is the dfcp_mi data
dfcp_mi_d2 <- complete(imp.d[[2]])
dfcp_mi_d3 <- complete(imp.d[[3]])
dfcp_mi_d4 <- complete(imp.d[[4]])
dfcp_mi_d5 <- complete(imp.d[[5]])


vis_miss(dfcp_mi_d2)

vis_miss(dfcp_mi_d3)

vis_miss(dfcp_mi_d4)

vis_miss(dfcp_mi_d5)

Optional Step: Remove Outliers

There can be valuable information in outliers and it is good practice to remove obvious errors only - such as impossible values for particular variables. No obvious errors identified.

Step 8: Select Features

library(caret)
#subset numerics for correlation only
#this will remove dependent variables also
dfcp_mi_cor <- dfcp_mi
dfcp_mi_cor <- select_if(dfcp_mi_cor, is.numeric)
cor(dfcp_mi_cor)

Co-correlation is indicated between st_ds_time_since_first_diagnosis and st_ds_time_since_first_diagnosis. Look at further feature selection methods as number of booleans are so high.

# ensure the results are reproducible
set.seed(7)
# load the library
library(mlbench)
Warning: package ‘mlbench’ was built under R version 4.2.3
# rf control
control <- rfeControl(functions=rfFuncs, method="cv", number=10)
# run the RFE algorithm against one of the target variables only
results <- rfe(dfcp_mi[,1:30],dfcp_mi[,34], sizes=c(1:30), rfeControl=control)
print(results)

Recursive feature selection

Outer resampling method: Cross-Validated (10 fold) 

Resampling performance over subset size:

The top 5 variables (out of 9):
   st_ds_time_since_first_diagnosis, st_ds_time_since_last_diagnosis, st_ds_number_of_diagnosis, dx_cancer, st_ds
# list features
predictors(results)
[1] "st_ds_time_since_first_diagnosis" "st_ds_time_since_last_diagnosis"  "st_ds_number_of_diagnosis"       
[4] "dx_cancer"                        "st_ds"                            "st_ds_number"                    
[7] "dx_hpv"                           "dx"                               "st_ds_molluscum_contagiosum"     

The variable st_ds_time_since_last_diagnosis was slightly lower in importance than the one it has high correlation with, so this one will be removed.

#remove redundant feature from all datasets

dfcp_mi <- dfcp_mi %>% select(-st_ds_time_since_last_diagnosis)
dfcp_cca <- dfcp_cca %>% select(-st_ds_time_since_last_diagnosis)
dfcp_mi_d2 <- dfcp_mi_d2 %>% select(-st_ds_time_since_last_diagnosis)
dfcp_mi_d3 <- dfcp_mi_d3 %>% select(-st_ds_time_since_last_diagnosis)
dfcp_mi_d4 <- dfcp_mi_d4 %>% select(-st_ds_time_since_last_diagnosis)
dfcp_mi_d5 <- dfcp_mi_d5 %>% select(-st_ds_time_since_last_diagnosis)

Optional step: Transform data

Scaling data allows models to compare relative relationships between data points more effectively.

#scale the numeric columns in all the datasets to be used in the test models

dfcp_mi <- dfcp_mi %>% mutate(across(where(is.numeric), scale))
dfcp_cca <- dfcp_cca %>% mutate(across(where(is.numeric), scale))
dfcp_mi_d5 <- dfcp_mi_d5 %>% mutate(across(where(is.numeric), scale))

Step 9: Build and Test Models

Create Training and Test Sets

Seeds set for reproducibility.

#mi data
set.seed(123)
index <- sample(2, nrow(dfcp_mi),
              replace = TRUE,
              prob = c(0.7, 0.3))
train_mi <- dfcp_mi[index==1,]
test_mi <- dfcp_mi[index==2,] 

#cca data
set.seed(123)
index1 <- sample(2, nrow(dfcp_cca),
                 replace = TRUE,
                 prob = c(0.7, 0.3))
train_cca <- dfcp_cca[index1==1,] 
test_cca <- dfcp_cca[index1==2,] 

#delta5 data
set.seed(123)
index5 <- sample(2, nrow(dfcp_mi_d5),
                 replace = TRUE,
                 prob = c(0.7, 0.3))
train_d5 <- dfcp_mi_d5[index5==1,] 
test_d5 <- dfcp_mi_d5[index5==2,] 

Predict Against Test Data

Start with baseline model using multiple imputation. Random Forest classification for each dependent variable.

library(randomForest)
randomForest 4.7-1.1
Type rfNews() to see new features/changes/bug fixes.

Attaching package: ‘randomForest’

The following object is masked from ‘package:psych’:

    outlier

The following object is masked from ‘package:dplyr’:

    combine

The following object is masked from ‘package:ggplot2’:

    margin
#Create x and y values
#dfcp_mi data
x <- train_mi[,-33] #remove dependent variable 
y <- as.factor(train_mi$biopsy) 
set.seed(345) #for reproducibility

#Fit model with training data and review details
rf = randomForest(x = x,
                  y = y,
                  ntree = 500)
rf

Call:
 randomForest(x = x, y = y, ntree = 500) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 5

        OOB estimate of  error rate: 1.99%
Confusion matrix:
    0  1 class.error
0 560  1 0.001782531
1  11 31 0.261904762
#predict results
xt <- test_mi[,-33] #remove dependent variable like class
yt <-as.factor(test_mi$biopsy) #keep class only
pred_yt <- predict(rf, newdata=xt)

#check accuracy
confusionMatrix(pred_yt, yt, mode="everything")
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 242   5
         1   0   8
                                          
               Accuracy : 0.9804          
                 95% CI : (0.9548, 0.9936)
    No Information Rate : 0.949           
    P-Value [Acc > NIR] : 0.009348        
                                          
                  Kappa : 0.7523          
                                          
 Mcnemar's Test P-Value : 0.073638        
                                          
            Sensitivity : 1.0000          
            Specificity : 0.6154          
         Pos Pred Value : 0.9798          
         Neg Pred Value : 1.0000          
              Precision : 0.9798          
                 Recall : 1.0000          
                     F1 : 0.9898          
             Prevalence : 0.9490          
         Detection Rate : 0.9490          
   Detection Prevalence : 0.9686          
      Balanced Accuracy : 0.8077          
                                          
       'Positive' Class : 0               
                                          
varImpPlot(rf)

CCA data model.

#Create x and y values
#dfcp_cca data
x1 <- train_cca[,-33]
y1 <- as.factor(train_cca$biopsy)
set.seed(345) #for reproducibility

#Fit model with training data and review details
rf1 = randomForest(x = x1,
                  y = y1,
                  ntree = 500)
rf1

Call:
 randomForest(x = x1, y = y1, ntree = 500) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 5

        OOB estimate of  error rate: 12.2%
Confusion matrix:
   0 1 class.error
0 35 0   0.0000000
1  5 1   0.8333333
#predict results
x1t <- test_cca[,-33] #remove dependent variable like class
y1t <-as.factor(test_cca$biopsy) #keep class only
pred_y1t <- predict(rf1, newdata=x1t)

#check accuracy
confusionMatrix(pred_y1t, y1t, mode="everything")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 15  2
         1  0  1
                                          
               Accuracy : 0.8889          
                 95% CI : (0.6529, 0.9862)
    No Information Rate : 0.8333          
    P-Value [Acc > NIR] : 0.4027          
                                          
                  Kappa : 0.4545          
                                          
 Mcnemar's Test P-Value : 0.4795          
                                          
            Sensitivity : 1.0000          
            Specificity : 0.3333          
         Pos Pred Value : 0.8824          
         Neg Pred Value : 1.0000          
              Precision : 0.8824          
                 Recall : 1.0000          
                     F1 : 0.9375          
             Prevalence : 0.8333          
         Detection Rate : 0.8333          
   Detection Prevalence : 0.9444          
      Balanced Accuracy : 0.6667          
                                          
       'Positive' Class : 0               
                                          
varImpPlot(rf1)

The Variable Importance plots are very different across the two models, due to the data that is lost through CCA.

Which Model Produced the Best Results?

The following code block captures the model which produced the best results, based on Kappa and core metric of balanced accuracy.

# set model variable for best performance as MI or CCA
better_model = "MI"

if(better_model=="MI" || better_model=="CCA") {print(paste(better_model, "produced the best result in this data experiment test."))
}else{
    print("Please enter either 'MI' or 'CCA' as the variable value for better_model.")}
[1] "MI produced the best result in this data experiment test."

Does Highest Delta Adjustment Have Big Impact on Results?

Code block included to edit for delta model.

#Create x and y values
#dfcp_cca data
x5 <- train_d5[,-33]
y5 <- as.factor(train_d5$biopsy)
set.seed(345) #for reproducibility

#Fit model with training data and review details
rf5 = randomForest(x = x5,
                  y = y5,
                  ntree = 500)
rf5

Call:
 randomForest(x = x5, y = y5, ntree = 500) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 5

        OOB estimate of  error rate: 5.31%
Confusion matrix:
    0  1 class.error
0 551 10  0.01782531
1  22 20  0.52380952
#test with test data
x5t <- test_d5[,-33]
y5t <-as.factor(test_d5$biopsy)
pred_y5t <- predict(rf5, newdata=x5t)

#check accuracy
confusionMatrix(pred_y5t, y5t, mode="everything")
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 238   3
         1   4  10
                                          
               Accuracy : 0.9725          
                 95% CI : (0.9443, 0.9889)
    No Information Rate : 0.949           
    P-Value [Acc > NIR] : 0.04973         
                                          
                  Kappa : 0.7263          
                                          
 Mcnemar's Test P-Value : 1.00000         
                                          
            Sensitivity : 0.9835          
            Specificity : 0.7692          
         Pos Pred Value : 0.9876          
         Neg Pred Value : 0.7143          
              Precision : 0.9876          
                 Recall : 0.9835          
                     F1 : 0.9855          
             Prevalence : 0.9490          
         Detection Rate : 0.9333          
   Detection Prevalence : 0.9451          
      Balanced Accuracy : 0.8764          
                                          
       'Positive' Class : 0               
                                          
varImpPlot(rf5)

The impact result is captured as a Yes or No response in the code below. The Kappa is lower but the Balanced Accuracy is higher as specificity is improved. There has been a big impact on results.

#Is there a big impact on results?
delta_impact = 'Yes'

if(delta_impact=="Yes" || delta_impact=="No") {print(paste(delta_impact, "is variable value for delta_impact."))
}else{
    print("Please enter either 'Yes' or 'No' as the variable value for delta_impact.")}
[1] "Yes is variable value for delta_impact."

Summary and Recommendations

The following code will print a summary, based on the variable information recorded in earlier code blocks.

Variable Parameters Set In Experiment

print(paste(missingness, "is missing data level."))
[1] "0.117 is missing data level."
print(paste("Is data more more likely to be missing in some variables than others?", likelihood))
[1] "Is data more more likely to be missing in some variables than others? Yes"
print(paste("Is data missing from dependent variable(s) only?", dependent_only))
[1] "Is data missing from dependent variable(s) only? No"
print(paste("Does higher delta adjustment have big impact on results?", delta_impact))
[1] "Does higher delta adjustment have big impact on results? Yes"

Is Data MCAR or MAR/MNAR?


if (likelihood=="No" & mcar_presult=="error") {
   hypothesis="Missing data pattern provides some support for MCAR hypothesis. However, no result available for MCAR Test."
   } else if (likelihood=="No" & mcar_presult<0.05) {
   hypothesis="Hypothesis unproven. MCAR Test and missing data pattern indicate different missing data mechanisms."
   } else if (likelihood=="No" & mcar_presult>=0.05) {
     hypothesis="MCAR hypothesis supported by MCAR Test and missing data pattern."
   } else if(likelihood=="Yes" & mcar_presult=="error") {
     hypothesis="Missing data pattern provides some support for MAR/MNAR hypothesis. However, no result available for MCAR Test."
   } else if (likelihood=="Yes" & mcar_presult<0.05) {
     hypothesis="MAR/MNAR hypothesis supported by MCAR Test and missing data pattern."
   }else if (likelihood=="Yes" & mcar_presult>=0.05) {
     hypothesis="Hypothesis unproven. MCAR Test and missing data pattern indicate different missing data mechanisms."
   }else {
   print("No hypothesis found.")
}

print(hypothesis)
[1] "Missing data pattern provides some support for MAR/MNAR hypothesis. However, no result available for MCAR Test."

Consideration of Test Model Results in Data Experiment


if (better_model=="MI" & approach==data_approach1) {
   print("For this particular data experiment, Multiple Imputation produced a more effective model than complete case analysis. However, there is a risk that it would not generalise well on new data.")
   } else if (better_model=="CCA" & approach==data_approach1) {
   print("For this particular data experiment, complete case analysis produced the most effective model. However, there is a risk that it would not generalise well on new data.")
   } else if (better_model=="MI" & approach==data_approach2) {
   print("For this particular data experiment, Multiple Imputation produced a more effective model than complete case analysis. However, complete case analysis would be likely to produce unbiased results also.")
   } else if (better_model=="CCA" & approach==data_approach2) {
   print("For this particular data experiment, complete case analysis produced the most effective model. However, multiple imputation may produce a more effective model in some circumstances.")
   } else if (better_model=="MI" & approach==data_approach3) {
   print("For this particular data experiment, Multiple Imputation produced a more effective model than complete case analysis. This result is expected, given variables provided.")
   } else if (better_model=="CCA" & approach==data_approach3) {
   print("For this particular data experiment, complete case analysis produced the most effective model. However, caution should be noted over results due to high level of missingness.")
   } else if (better_model=="MI" & approach==data_approach4) {
   print("For this particular data experiment, Multiple Imputation produced a more effective model than complete case analysis. However, caution should be taken with how missing data in dependent variables is handled, as CCA may be more reliable.")
   } else if (better_model=="CCA" & approach==data_approach4) {
   print("For this particular data experiment, complete case analysis produced the most effective model. This should be a reliable approach for this missing data problem.")
   } else if (better_model=="MI" & approach==data_approach5) {
   print("For this particular data experiment, Multiple Imputation produced a more effective model than complete case analysis. This should be a reliable approach for this missing data problem.")
   } else if (better_model=="CCA" & approach==data_approach5) {
   print("For this particular data experiment, complete case analysis produced the most effective model. However, the MCAR hypothesis is either not supported or unclear so caution is advised on the results produced.")
   } else if (better_model=="MI" & approach==data_approach6) {
   print("For this particular data experiment, Multiple Imputation produced a more effective model than complete case analysis. However, it is recommended that a pattern mixture model is considered also.")
   } else if (better_model=="CCA" & approach==data_approach6) {
   print("For this particular data experiment, complete case analysis produced the most effective model. However, it is recommended that a pattern mixture model is considered also.")
   } else {
   print("No model evaluation found.")
}
[1] "For this particular data experiment, Multiple Imputation produced a more effective model than complete case analysis. However, it is recommended that a pattern mixture model is considered also."

References

–Last updated: August 2023
–End–

LS0tDQp0aXRsZTogIlZhYXIgUiBOb3RlYm9vazogQ2VydmljYWwgQ2FuY2VyIChSaXNrIEZhY3RvcnMpIg0KYXV0aG9yOiAiQW1hbmRhIEhhcnJpcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KcGFyYW1zOg0KICBwcmludGNvZGU6IHRydWUNCi0tLQ0KDQpUaGlzIGlzIGFuIGV4ZW1wbGFyIHdvcmtib29rIHNob3dpbmcgaG93IHRoZSBWYWFyIE5vdGVib29rIGNhbiBiZSBhcHBsaWVkIHRvIFVDSSdzIENlcnZpY2FsIENhbmNlciAoUmlzayBGYWN0b3JzKSBkYXRhc2V0IChGZXJuYW5kZXMgZXQgYWwuLCAyMDE3KSB0byByZXR1cm46ICANCi0gIFdoZXRoZXIgZGF0YSBpcyBtb3JlIGxpa2VseSB0byBiZSBNQ0FSIG9yIE1BUi9NTkFSICANCi0gIEEgcmVjb21tZW5kZWQgYXBwcm9hY2ggZm9yIG1hbmFnaW5nIG1pc3NpbmcgZGF0YSB0aGF0IGNvbnNpZGVycyBzdGFiaWxpdHkgb2YgbW9kZWwgcmVzdWx0cyAgDQotICBFdmFsdWF0aW9uIG9mIGltcHV0YXRpb24gZWZmb3J0cyBiYXNlZCBvbiBkYXRhIG1vZGVsIGV4cGVyaW1lbnQgcmVzdWx0cyAgDQoNCiMgU3RlcCAxOiBSZWFkIGluIGRhdGEgYW5kIHNob3cgZGF0YSBzdW1tYXJ5ICANCg0KRGF0YS50YWJsZSBwYWNrYWdlIHVzZWQgdG8gcmVhZCBpbiBmaWxlIGZyb20gd2Vic2l0ZS4oRmVybmFuZGVzIGV0IGFsLiwgMjAxNykNCg0KYGBge3J9DQpsaWJyYXJ5KGRhdGEudGFibGUpDQpkZiA8LSBmcmVhZCgnaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzLzAwMzgzL3Jpc2tfZmFjdG9yc19jZXJ2aWNhbF9jYW5jZXIuY3N2JywgaGVhZGVyPVRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCmhlYWQoZGYpDQpzdW1tYXJ5KGRmKQ0KDQpgYGANCkxvdHMgb2YgdmFyaWFibGVzIGltcG9ydGVkIGFzIGNoYXJhY3RlcnMgYXMgdGhlcmUgaXMgbWlzc2luZyBkYXRhIGFuZCBsb3RzIG9mIGJvb2xlYW4gdmFyaWFibGVzIGFsc28uIA0KYGBge3J9DQpzdHIoZGYpDQpgYGANCg0KIyBTdGVwIDI6IENvcHkgYW5kIFRpZHkgRGF0YQ0KDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGphbml0b3IpDQpsaWJyYXJ5KG5hbmlhcikNCiNjbGVhbiB2YXJpYWJsZSBuYW1lcw0KZGYgPC0gZGYgJT4lDQogICAgICAgIGNsZWFuX25hbWVzKCkNCmRmY3AgPC0gZGYgI2NvcHkgZGF0YQ0KDQojYXNzaWduIHZhbHVlIE5BIHRvICI/Ig0KZGZjcCA8LSBkZmNwICU+JSByZXBsYWNlX3dpdGhfbmFfYWxsKGNvbmRpdGlvbiA9IH4ueCA9PSAiPyIpDQoNCmBgYA0KIyMjIyBSZW1vdmUgdmFyaWFibGVzIHdpdGggbm8gdmFyaWFuY2UNClN1bW1hcnkgaWRlbnRpZmllZCB0aGF0IHN0X2RzX2NlcnZpY2FsX2NvbmR5bG9tYXRvc2lzIGFuZCBzdF9kc19haWRzIGhhdmUgbm8gdmFyaWFuY2UuIFJlbW92aW5nIHRoZW0gZnJvbSBkYXRhZnJhbWUgYXMgdGhlcmUgYXJlIGxvdHMgb2YgdmFyaWFibGVzIGluIHRoaXMgZGF0YSBhbmQgdGhlc2Ugd2lsbCBub3QgYWRkIHZhbHVlIHRvIHRoZSBtb2RlbC4NCmBgYHtyfQ0KZGZjcCA8LSBzZWxlY3QoZGZjcCwtYyhzdF9kc19haWRzKSkNCmRmY3AgPC0gc2VsZWN0KGRmY3AsLWMoc3RfZHNfY2VydmljYWxfY29uZHlsb21hdG9zaXMpKQ0KYGBgDQoNCg0KDQojIyBDaGVjayBmb3IgRHVwbGljYXRlcw0KRHVwbGljYXRlcyBjaGVja2VkIHVzaW5nIG5fZGlzdGluY3QgaW4gdGlkeXZlcnNlIHBhY2thZ2UNCg0KYGBge3J9DQpuX2Rpc3RpbmN0KGRmY3ApDQoNCiNmaWx0ZXIgdG8gY2hlY2sgYW5kIHJldmlldyBhbnkgZHVwbGljYXRlIHJlY29yZHMNCmR1cGxpY2F0ZXMgPC0gZGZjcCAlPiUNCiAgZmlsdGVyKGR1cGxpY2F0ZWQoLikpDQoNCnByaW50KGR1cGxpY2F0ZXMpDQpgYGANCg0KIyMjIFJlbW92ZSBkdXBsaWNhdGVzIGFuZCBhbnkgdW5uZWNlc3NhcnkgdmFyaWFibGVzIGZvciBtb2RlbCBzdWNoIGFzIGlkDQpUaGVyZSBhcmUgbm8gdW5pcXVlIGlkZW50aWZpZXJzLCBzbyB0aGVzZSBtYXkgbm90IGJlIGR1cGxpY2F0ZXMgYW5kIHdpbGwgbm90IGJlIHJlbW92ZWQuDQoNCiMgU3RlcCAzOiBWaXN1YWxpc2UgTWlzc2luZyBEYXRhIGFuZCBydW4gTUNBUiBUZXN0DQoNClBhY2thZ2VzIHVzZWQ6IHZpc2RhdCB0byBleHBsb3JlIG1pc3NpbmcgdmFsdWVzLCBnZ3Bsb3QgdG8gZXhwbG9yZSBtaXNzaW5nIGRhdGEgZnVydGhlciBhbmQgbmFuaWFyIGZvciBnZW9tX21pc3NfcG9pbnQgZnVuY3Rpb24gYW5kIE1DQVIgdGVzdC4gIA0KKipUaGVyZSB3aWxsIGJlIGEgd2FybmluZyB3aXRoIE1DQVIgdGVzdCBpZiBub24tbnVtZXJpYyBjb2x1bW5zIGFyZSBwcmVzZW50LiBJZiBwLXZhbHVlID4wLjA1IGxpa2VseSB0byBiZSBNQ0FSIGFzIG5vdCBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LioqDQpUaGUgbmFuaWFyIHRlc3QgbWF5IGFsc28gZXJyb3IgZHVlIHRvIHNpbmd1bGFyIGRhdGEgYW5kIGEgZml4IGhhcyBub3QgYmVlbiBmb3VuZCBmb3IgdGhpcyB5ZXQgKEF1Z3VzdCAyMDIzKS4gSG93ZXZlciwgdGhlIGRhdGEgb3duZXJzIChGZXJuYW5kZXMgZXQgYWwuLCAyMDE3KSBhZHZpc2UgdGhhdCBzZXZlcmFsIHBhdGllbnRzIGRpZCBub3QgYW5zd2VyIHF1ZXN0aW9ucyBkdWUgdG8gcHJpdmFjeSBjb25jZXJucyBpbmRpY2F0aW5nIHRoYXQgc29tZSBvZiB0aGUgZGF0YSBpcyBsaWtlbHkgdG8gYmUgTU5BUi4NCg0KVGhlcmUgYXJlIGZvdXIgdGFyZ2V0IHZhcmlhYmxlcyBpbiB0aGlzIGRhdGFzZXQ6IEhpbnNlbG1hbm4sIFNjaGlsbGVyLCBDeXRvbG9neSBhbmQgQmlvcHN5LihTaW5kaWFuaSBBTSBldCBhbCwgMjAyMCkgaW52ZXN0aWdhdGVkIHRoZSByaXNrIGZhY3RvcnMgYXNzb2NpYXRlZCB3aXRoIGNlcnZpY2FsIGNhbmNlciB0byBzdHVkeSB0aGVpciBwb3NzaWJsZSBhc3NvY2lhdGlvbiB3aXRoIHRoZSBkZWNpc2lvbiB0byB0YWtlIGEgY2VydmljYWwgYmlvcHN5LiBUaGUgZGF0YSBwcm9ibGVtIHRvIGJlIHRlc3RlZCBpbiB0aGlzIG5vdGVib29rIGlzIHRoZSBhYmlsaXR5IHRvIGFjY3VyYXRlbHkgY2xhc3NpZnkgdGhlIGRlY2lzaW9uIHRvIHRha2UgYSBjZXJ2aWNhbCBiaW9wc3kuIA0KDQpgYGB7cn0NCmxpYnJhcnkodmlzZGF0KQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQp2aXNfbWlzcyhkZmNwKQ0KbWlzc192YXJfc3VtbWFyeShkZmNwKQ0KDQojbWNhcl90ZXN0KGRmY3ApICMgY29tcHV0YXRpb25hbGx5IHNpbmd1bGFyDQpgYGANCg0KIyBTdGVwIDQ6IExvb2sgYXQgT3V0cHV0cyBmcm9tIFZpc3VhbGlzaW5nIHRoZSBNaXNzaW5nIERhdGEgYW5kIEFuc3dlciB0aGUgRm9sbG93aW5nIFF1ZXN0aW9ucw0KDQojIyMjIFdoYXQgaXMgdGhlIHAudmFsdWUgcmV0dXJuZWQgYnkgdGhlIE1DQVIgVGVzdD8NCklmIHRoZSBzdGF0aXN0aWMgaXMgaGlnaCBhbmQgdGhlIHAudmFsdWUgPDAuMDUgaXQgaXMgbGlrZWx5IHRvIGJlIE1OQVIgb3IgTUFSIGFuZCBjYW5ub3QgYmUgaWdub3JlZC4gQSBwLXZhbHVlID4wLjA1IGluZGljYXRlcyBNQ0FSIGJ1dCBvdGhlciBpbmZvcm1hdGlvbiB3aWxsIGJlIGNvbnNpZGVyZWQgYWxzby4gVGhlIHZhbHVlIGlzIHNldCBhcyBhIHZhcmlhYmxlIGluIHRoZSBmb2xsb3dpbmcgY29kZS4gQXMgdGhlIE1DQVIgVGVzdCBlcnJvcmVkIGR1ZSB0byBkYXRhIHNpbmd1bGFyaXR5LCAiZXJyb3IiIGhhcyBiZWVuIGVudGVyZWQgaGVyZS4NCmBgYHtyfQ0KI3NldCBNQ0FSIFRlc3QgcC52YWx1ZSBhcyB2YXJpYWJsZQ0KbWNhcl9wcmVzdWx0ID0gImVycm9yIg0KDQppZihtY2FyX3ByZXN1bHQ9PSJlcnJvciIgfHwgaXMubnVtZXJpYyhtY2FyX3ByZXN1bHQpKXtwcmludChwYXN0ZShtY2FyX3ByZXN1bHQsICJpcyBNQ0FSIFRlc3QgcC52YWx1ZSB2YXJpYWJsZSB2YWx1ZS4iKSkNCn1lbHNlew0KICAgIHByaW50KCJQbGVhc2UgZW50ZXIgZWl0aGVyIGEgbnVtYmVyIG9yICdlcnJvcicgYXMgdGhlIG1jYXJfcHJlc3VsdCB2YXJpYWJsZSB2YWx1ZS4iKX0NCg0KYGBgDQoNCg0KIyMjIyBJcyBkYXRhIG1vcmUgbGlrZWx5IHRvIGJlIG1pc3NpbmcgaW4gc29tZSB2YXJpYWJsZXM/DQpJZiBzbywgdGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgZGF0YSBjb3VsZCBiZSBtaXNzaW5nIGF0IHJhbmRvbSAoTUFSKSBvciBtaXNzaW5nIG5vdCBhdCByYW5kb20gKE1OQVIpIGFuZCBjYW4gdGhlcmVmb3JlIG5vdCBiZSBpZ25vcmVkLiBZZXMgb3IgTm8gaXMgc2V0IGFzIGEgdmFyaWFibGUgaW4gdGhlIGZvbGxvd2luZyBjb2RlLg0KYGBge3J9DQojc2V0IGxpa2VsaWhvb2QgdmFyaWFibGUNCmxpa2VsaWhvb2QgPSAiWWVzIg0KDQppZihsaWtlbGlob29kPT0iWWVzIiB8fCBsaWtlbGlob29kPT0iTm8iKSB7cHJpbnQocGFzdGUobGlrZWxpaG9vZCwgImlzIHZhcmlhYmxlIHZhbHVlIGZvciBsaWtlbGlob29kLiIpKQ0KfWVsc2V7DQogICAgcHJpbnQoIlBsZWFzZSBlbnRlciBlaXRoZXIgJ1llcycgb3IgJ05vJyBhcyB0aGUgdmFyaWFibGUgdmFsdWUgZm9yIGxpa2VsaWhvb2QuIil9DQpgYGANCg0KIyMjIyBJcyBkYXRhIG1pc3NpbmcgZnJvbSB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIG9ubHk/DQpJZiBzbywgdGhpcyBzdWdnZXN0cyB0aGF0IGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgbWF5IGJlIHRoZSBiZXN0IG9wdGlvbi4gSG93ZXZlciwgb3RoZXIgZmFjdG9ycyBuZWVkIHRvIGJlIGNvbnNpZGVyZWQgYWxzby4gWWVzIG9yIE5vIGlzIHNldCBhcyB2YXJpYWJsZSBpbiB0aGUgZm9sbG93aW5nIGNvZGUuDQpgYGB7cn0NCiNzZXQgZGVwZW5kZW50IG1pc3NpbmduZXNzIHZhcmlhYmxlDQpkZXBlbmRlbnRfb25seSA9ICJObyINCg0KaWYoZGVwZW5kZW50X29ubHk9PSJZZXMiIHx8IGRlcGVuZGVudF9vbmx5PT0iTm8iKSB7cHJpbnQocGFzdGUoZGVwZW5kZW50X29ubHksICJpcyB2YXJpYWJsZSB2YWx1ZSBmb3IgZGVwZW5kZW50X29ubHkuIikpDQp9ZWxzZXsNCiAgICBwcmludCgiUGxlYXNlIGVudGVyIGVpdGhlciAnWWVzJyBvciAnTm8nIGFzIHRoZSB2YXJpYWJsZSB2YWx1ZSBmb3IgZGVwZW5kZW50X29ubHkuIil9DQpgYGANCg0KDQojIyMjIFdoYXQgcHJvcG9ydGlvbiBvZiBkYXRhIGlzIG1pc3Npbmc/DQpJZiBpdCdzIGJldHdlZW4gMC4xLTAuNSBtdWx0aXBsZSBpbXB1dGF0aW9uIGlzIGxpa2VseSB0byByZXN1bHQgaW4gYSBtb3JlIGVmZmVjdGl2ZSwgdW5iaWFzZWQgbW9kZWwuIFRoZSB2YWx1ZSBpcyBzZXQgYXMgYSB2YXJpYWJsZSBpbiB0aGUgZm9sbG93aW5nIGNvZGUgaW4gdGhlIGZvcm1hdCAwLjAwMC4gVGhpcyBpcyB0YWtlbiBmcm9tIHRoZSBtaXNzaW5nIGRhdGEgdmlzdWFsaXNhdGlvbi4NCg0KYGBge3J9DQojc2V0IG1pc3NpbmduZXNzIHZhcmlhYmxlIHZhbHVlDQptaXNzaW5nbmVzcyA9IDAuMTE3DQoNCmlmKGlzLm51bWVyaWMobWlzc2luZ25lc3MpKXtwcmludChwYXN0ZShtaXNzaW5nbmVzcywgImlzIG1pc3NpbmduZXNzIHZhcmlhYmxlIHZhbHVlLiIpKQ0KfWVsc2V7DQogICAgcHJpbnQoIlBsZWFzZSBlbnRlciBhIG51bWJlciBhcyB0aGUgbWlzc2luZ25lc3MgdmFyaWFibGUgdmFsdWUuIil9DQpgYGANCg0KIyMjIyMgSXQncyBnb29kIHByYWN0aWNlIHRvIHRlc3QgZGlmZmVyZW50IGFwcHJvYWNoZXMgYW5kIHRoZSBWYWFyIE5vdGVib29rcyB3aWxsIGNvbnNpZGVyIHRoZXNlLCBhcyB3ZWxsIGFzIHRoZSB2YXJpYWJsZSB2YWx1ZXMganVzdCBzZXQgaW4gcmVjb21tZW5kYXRpb25zLg0KDQoNCiMjIENvcnJlbGF0aW9uDQpDb3JyZWxhdGlvbiBmb3IgbnVtZXJpY2FsIHZhcmlhYmxlcy5UaGVyZSBhcmUgYSBsYXJnZSBudW1iZXIgb2YgdmFyaWFibGVzIGFuZCBpbmRpY2F0aW9ucyBvZiBjby1jb3JyZWxhdGlvbiBpbiB0aGUgbWF0cml4LCBzbyBmZWF0dXJlIHNlbGVjdGlvbiB3aWxsIGJlIGltcG9ydGFudC4NCg0KYGBge3J9DQojc3Vic2V0IG51bWVyaWNzIGZvciBjb3JyZWxhdGlvbiBvbmx5DQojdGhpcyB3aWxsIHJlbW92ZSBkZXBlbmRlbnQgdmFyaWFibGVzIGFsc28NCmRmY3BfY29yIDwtIHNlbGVjdF9pZihkZmNwLCBpcy5udW1lcmljKQ0KY29yKGRmY3BfY29yKQ0KYGBgDQojIyBDYWxjdWxhdGUgU2tldw0KYGBge3J9DQpsaWJyYXJ5KHBzeWNoKSAjZm9yIHNrZXduZXNzIGZ1bmN0aW9uDQpza2V3KGRmY3BfY29yKQ0KYGBgDQoNCiMgU3RlcCA1OiB3aGljaCB2YXJpYWJsZXMgYW5kIHZhbHVlcyBhcmUgaW1wb3J0YW50IGZvciBwcmVkaWN0aW5nIHByb3BvcnRpb24gb2YgbWlzc2luZ25lc3M/DQoNCnJwYXJ0IGFuZCBycGFydC5wbG90IHBhY2thZ2VzIGFyZSB1c2VkIHRvIHBsb3QgYSBzaW1wbGUgY2xhc3NpZmljYXRpb24gdHJlZS4NCg0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KDQpkZmNwICU+JQ0KICBhZGRfcHJvcF9taXNzKCkgJT4lDQogIHJwYXJ0KHByb3BfbWlzc19hbGwgfiAuLCBkYXRhPS4pICU+JQ0KICBwcnAodHlwZT00LCBleHRyYSA9IDEwMSwgcm91bmRpbnQ9RkFMU0UsIHByZWZpeD0iUHJvcC5NaXNzID0gIikNCmBgYA0KDQojIyMjIFdoaWNoIG51bWVyaWNhbCB2YXJpYWJsZSB3aXRoIG1pc3NpbmcgZGF0YSBpcyBuZWFyZXN0IHRoZSB0b3Agb2YgdGhlIHRyZWU/DQpWYXJpYWJsZSB2YWx1ZSBmb3IgbWlzc2luZ25lc3MgaW5mbHVlbmNlciBzZXQgaW4gY29kZSBiZWxvdy4NCmBgYHtyfQ0KI3NldCB2YWx1ZSBmb3IgbWlzc2luZ25lc3MgaW5mbHVlbmNlcg0KbWlzc19pbmZsdWVuY2VyPSJzdF9kc19udW1iZXJfb2ZfZGlhZ25vc2lzIg0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShtaWNlKQ0KZnhwdCA8LSBmbHV4cGxvdChkZmNwKQ0KIyBWYXJpYWJsZXMgd2l0aCBoaWdoZXIgb3V0Zmx1eCBtYXkgYmUgbW9yZSBwb3dlcmZ1bCBwcmVkaWN0b3JzLiBNb3JlIG1lYW5pbmdmdWwgd2hlcmUgZGF0YSBpcyBtaXNzaW5nIGZyb20gbW9yZSB0aGFuIG9uZSB2YXJpYWJsZS4NCmBgYA0KDQoNCiMjIyMgQ2hhbmdlIENvbHVtbnMgdG8gRmFjdG9ycyBXaGVyZSBOZWNlc3NhcnkNCmBgYHtyfQ0KZGZjcCA8LSBhcy5kYXRhLmZyYW1lKGRmY3ApIA0KDQpkZmNwIDwtIGRmY3AgICU+JSBtdXRhdGUoYWNyb3NzKGMoInNtb2tlcyIsICJob3Jtb25hbF9jb250cmFjZXB0aXZlcyIsICJpdWQiLCAic3RfZHMiLCAic3RfZHNfY29uZHlsb21hdG9zaXMiLCAic3RfZHNfdmFnaW5hbF9jb25keWxvbWF0b3NpcyIsICJzdF9kc192dWx2b19wZXJpbmVhbF9jb25keWxvbWF0b3NpcyIsICJzdF9kc19zeXBoaWxpcyIsICJzdF9kc19wZWx2aWNfaW5mbGFtbWF0b3J5X2Rpc2Vhc2UiLCAic3RfZHNfZ2VuaXRhbF9oZXJwZXMiLCAic3RfZHNfbW9sbHVzY3VtX2NvbnRhZ2lvc3VtIiwgInN0X2RzX2hpdiIsICJzdF9kc19oZXBhdGl0aXNfYiIsICJzdF9kc19ocHYiLCAiZHhfY2FuY2VyIiwgImR4X2NpbiIsICJkeF9ocHYiLCAiZHgiLCAiaGluc2VsbWFubiIsICJzY2hpbGxlciIsICJjaXRvbG9neSIsICJiaW9wc3kiKSwgYXMuZmFjdG9yKSkNCg0KYGBgDQoNCiMjIyMgQ2hhbmdlIENvbHVtbnMgdG8gSW50ZWdlcnMgV2hlcmUgTmVjZXNzYXJ5DQpgYGB7cn0NCmRmY3AgPC0gZGZjcCAgJT4lIG11dGF0ZShhY3Jvc3MoYygibnVtYmVyX29mX3NleHVhbF9wYXJ0bmVycyIsICJmaXJzdF9zZXh1YWxfaW50ZXJjb3Vyc2UiLCAibnVtX29mX3ByZWduYW5jaWVzIiwgInNtb2tlc195ZWFycyIsICJzbW9rZXNfcGFja3NfeWVhciIsImhvcm1vbmFsX2NvbnRyYWNlcHRpdmVzX3llYXJzIiwgIml1ZF95ZWFycyIsICJzdF9kc19udW1iZXIiLCJzdF9kc190aW1lX3NpbmNlX2ZpcnN0X2RpYWdub3NpcyIsICJzdF9kc190aW1lX3NpbmNlX2xhc3RfZGlhZ25vc2lzIiksIGFzLmludGVnZXIpKQ0KYGBgDQoNCg0KIyBTdGVwIDY6IENyZWF0ZSBDb21wbGV0ZSBDYXNlIGFuZCBNdWx0aXBsZSBJbXB1dGVkIERhdGFzZXRzDQpDcmVhdGUgY29tcGxldGUgZGF0YXNldCBieSBkZWxldGluZyByZWNvcmRzIHdpdGggbWlzc2luZyB2YWx1ZXMgYW5kIHJ1biBNdWx0aXBsZSBJbXB1dGF0aW9uIHVzaW5nIE1JQ0UuDQoNCmBgYHtyfQ0KZGZjcF9jY2EgPC0gZGZjcFtjb21wbGV0ZS5jYXNlcyhkZmNwKSwgXSANCg0KaW5pdCA9IG1pY2UoZGZjcCwgbWF4aXQ9MTApDQptZXRoID0gaW5pdCRtZXRob2Q9YygicG1tIiwgInBtbSIsICJwbW0iLCAicG1tIiwgImxvZ3JlZyIsICJwbW0iLCAicG1tIiwgImxvZ3JlZyIsICJwbW0iLCAibG9ncmVnIiwgInBtbSIsICJsb2dyZWciLCAicG1tIiwgImxvZ3JlZyIsICJsb2dyZWciLCAibG9ncmVnIiwgImxvZ3JlZyIsICJsb2dyZWciLCAibG9ncmVnIiwgImxvZ3JlZyIsICJsb2dyZWciLCAibG9ncmVnIiwgImxvZ3JlZyIsICJwbW0iLCAicG1tIiwgInBtbSIsICJsb2dyZWciLCAibG9ncmVnIiwgImxvZ3JlZyIsICJsb2dyZWciLCAibG9ncmVnIiwgImxvZ3JlZyIsICJsb2dyZWciLCAibG9ncmVnIikNCnByZWRNID0gaW5pdCRwcmVkaWN0b3JNYXRyaXgNCg0KI2NyZWF0ZSBpbXB1dGVkIGRhdGENCnNldC5zZWVkKDEyMykNCmRmY3BfaW1wID0gbWljZShkZmNwLCBtZXRob2Q9bWV0aCwgcHJlZGljdG9yTWF0cml4PXByZWRNLCBtPTUsIG5uZXQuTWF4Tld0cz0zMDAwKQ0KI2NyZWF0ZSBkYXRhc2V0IGFmdGVyIGltcHV0YXRpb24NCmRmY3BfbWkgPC0gY29tcGxldGUoZGZjcF9pbXApDQoNCmBgYA0KV2FybmluZ3MgZ2l2ZW4sIHZpc3VhbGlzYXRpb25zIGNyZWF0ZWQgdG8gZW5zdXJlIGNvbXBsZXRlIGRhdGEgcmV0dXJuZWQuDQpgYGB7cn0NCiNjaGVjayBmb3IgbWlzc2luZyBkYXRhIGluIE1JIGFuZCBDQ0EgZGF0YXNldA0KbGlicmFyeSh2aXNkYXQpDQp2aXNfbWlzcyhkZmNwX21pKQ0KdmlzX21pc3MoZGZjcF9jY2EpDQpgYGANCg0KDQoNCiMgU3RlcCA3OiBDb25kdWN0IE1OQVIgU2Vuc2l0aXZpdHkgVGVzdA0KDQpVc2UgdGhlIHZhcmlhYmxlIHdpdGggbWlzc2luZyBkYXRhIHdoaWNoIGhhcyB0aGUgbW9zdCBpbmZsdWVuY2Ugb24gcHJvcG9ydGlvbiBvZiBtaXNzaW5nbmVzcywgYWNjb3JkaW5nIHRvIHN0ZXAgNSBhbmQgbG9vayBhdCB0aGUgcmFuZ2Ugb2YgdmFsdWVzIGluIHRoaXMgdmFyaWFibGUuIEdlbmVyYXRlIGltcHV0YXRpb25zIHVuZGVyIGRlbHRhIGFkanVzdG1lbnQgdG8gaW1pdGF0ZSBkZXZpYXRpb25zIGZyb20gTUFSIChHaW5rIGFuZCBWYW4gQnV1cmVuLCBubyBkYXRlKS4gVGhlIGNvZGUgdXNlcyAwIGZvciBNQVIgYW5kIGEgcmVhc29uYWJsZSByYW5nZSBiYXNlZCBvbiB0aGUgdmFyaWFibGUgcmFuZ2UgYXMgdGhlIGRlbHRhIHZhbHVlcyB0byB0ZXN0IE1OQVIuDQoNCmBgYHtyfQ0KDQojR2VuZXJhdGUgaW1wdXRhdGlvbnMgdW5kZXIgZGVsdGEgYWRqdXN0bWVudA0KZGVsdGEgPC0gYygwLCArMC4yLCArMC40LCArMC42LCArMC44KSAjbWVhbiBpcyAwLjgsIG1heCBpcyAzDQppbXAuZCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgoZGVsdGEpKQ0KcG9zdCA8LWRmY3BfaW1wJHBvc3QNCg0KZm9yIChpIGluIDE6bGVuZ3RoKGRlbHRhKSkgew0KICBkIDwtIGRlbHRhW2ldDQogIGNtZCA8LSBwYXN0ZSgiaW1wW1tqXV1bLGldIDwtIGltcFtbal1dWyxpXSArIiwgZCkNCiAgcG9zdFsic3RfZHNfbnVtYmVyX29mX2RpYWdub3NpcyJdIDwtIGNtZA0KICBpbXAgPC0gbWljZShkZmNwLCBwb3N0ID0gcG9zdCwgbWF4aXQgPSA1LA0KICAgICAgICAgICAgICBzZWVkID0gaSwgcHJpbnQ9RkFMU0UpDQogIGltcC5kW1tpXV0gPC0gaW1wDQp9DQpgYGANCg0KIyMgSW5zcGVjdCBJbXB1dGF0aW9ucw0KDQpUaGUgZmlyc3QgcGxvdCBpcyBiYXNlZCBvbiBubyBkZWx0YSBhZGp1c3RtZW50IGFuZCB0aGUgc2Vjb25kIHBsb3QgaXMgdGhlIGhpZ2hlc3QgYWRqdXN0bWVudC4gVGhlIFkgYXhpcyBzY2FsZSBpcyBzZXQgdG8gdGhlIHNhbWUgdmFsdWUsIHNvIHRoYXQgZGlmZmVyZW5jZXMgY2FuIGJlIHZpc3VhbGlzZWQgbW9yZSBlYXNpbHkuIA0KDQpgYGB7cn0NCmRlbnNpdHlwbG90KGltcC5kW1sxXV0sbHdkPTMsIHlsaW09YygwLDMpKSANCmRlbnNpdHlwbG90KGltcC5kW1s1XV0sbHdkPTMsIHlsaW09YygwLDMpKSANCmBgYA0KDQpGaW5kIG91dCBtb3JlIGFib3V0IHNlbnNpdGl2aXR5IGFuYWx5c2lzIGluIHRoZSBjb250ZXh0IG9mIG1pc3NpbmcgZGF0YSBmcm9tIEdlcmtvIFZpbmsgYW5kIFN0ZWYgdmFuIEJ1dXJlbidzIHZpZ25ldHRlIFttaWNlOiBBbiBhcHByb2FjaCB0byBzZW5zaXRpdml0eSBhbmFseXNpc10oaHR0cHM6Ly93d3cuZ2Vya292aW5rLmNvbS9taWNlVmlnbmV0dGVzL1NlbnNpdGl2aXR5X2FuYWx5c2lzL1NlbnNpdGl2aXR5X2FuYWx5c2lzLmh0bWwpIChWaW5rIGFuZCB2YW4gQnV1cmVuLCBubyBkYXRlKS4gDQoNClRoZSBzZWNvbmQgZGVuc2l0eSBwbG90IGNvbnNpZGVycyBpbXB1dGF0aW9ucyB1bmRlciB0aGUgbGFyZ2VzdCBhZGp1c3RtZW50LCBsb29raW5nIGF0IHRoZSBibHVlIGxpbmUgaW4gcGFydGljdWxhciB0aGVyZSBkb2VzIHNlZW0gdG8gYmUgc29tZSBjaGFuZ2UuDQoNCiMjIENyZWF0ZSBDb21wbGV0ZSBEYXRhc2V0cyB3aXRoIERlbHRhIEltcHV0YXRpb25zIDEgdG8gNQ0KQ3JlYXRlIGNvbXBsZXRlIGRhdGFzZXRzIGFuZCBjaGVjayBpbXB1dGF0aW9ucyBoYXZlIHdvcmtlZCBieSB2aXN1YWxpc2luZyBtaXNzaW5nIGRhdGEuDQoNCmBgYHtyfQ0KI2RlbHRhIDEgaXMgdGhlIGRmY3BfbWkgZGF0YQ0KZGZjcF9taV9kMiA8LSBjb21wbGV0ZShpbXAuZFtbMl1dKQ0KZGZjcF9taV9kMyA8LSBjb21wbGV0ZShpbXAuZFtbM11dKQ0KZGZjcF9taV9kNCA8LSBjb21wbGV0ZShpbXAuZFtbNF1dKQ0KZGZjcF9taV9kNSA8LSBjb21wbGV0ZShpbXAuZFtbNV1dKQ0KDQoNCnZpc19taXNzKGRmY3BfbWlfZDIpDQp2aXNfbWlzcyhkZmNwX21pX2QzKQ0KdmlzX21pc3MoZGZjcF9taV9kNCkNCnZpc19taXNzKGRmY3BfbWlfZDUpDQoNCmBgYA0KDQojIyBPcHRpb25hbCBTdGVwOiBSZW1vdmUgT3V0bGllcnMNClRoZXJlIGNhbiBiZSB2YWx1YWJsZSBpbmZvcm1hdGlvbiBpbiBvdXRsaWVycyBhbmQgaXQgaXMgZ29vZCBwcmFjdGljZSB0byByZW1vdmUgb2J2aW91cyBlcnJvcnMgb25seSAtIHN1Y2ggYXMgaW1wb3NzaWJsZSB2YWx1ZXMgZm9yIHBhcnRpY3VsYXIgdmFyaWFibGVzLiBObyBvYnZpb3VzIGVycm9ycyBpZGVudGlmaWVkLg0KDQoNCiMgU3RlcCA4OiBTZWxlY3QgRmVhdHVyZXMNCg0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KI3N1YnNldCBudW1lcmljcyBmb3IgY29ycmVsYXRpb24gb25seQ0KI3RoaXMgd2lsbCByZW1vdmUgZGVwZW5kZW50IHZhcmlhYmxlcyBhbHNvDQpkZmNwX21pX2NvciA8LSBkZmNwX21pDQpkZmNwX21pX2NvciA8LSBzZWxlY3RfaWYoZGZjcF9taV9jb3IsIGlzLm51bWVyaWMpDQpjb3IoZGZjcF9taV9jb3IpDQoNCmBgYA0KQ28tY29ycmVsYXRpb24gaXMgaW5kaWNhdGVkIGJldHdlZW4gc3RfZHNfdGltZV9zaW5jZV9maXJzdF9kaWFnbm9zaXMgYW5kIHN0X2RzX3RpbWVfc2luY2VfZmlyc3RfZGlhZ25vc2lzLiBMb29rIGF0IGZ1cnRoZXIgZmVhdHVyZSBzZWxlY3Rpb24gbWV0aG9kcyBhcyBudW1iZXIgb2YgYm9vbGVhbnMgYXJlIHNvIGhpZ2guDQpgYGB7cn0NCiMgZW5zdXJlIHRoZSByZXN1bHRzIGFyZSByZXByb2R1Y2libGUNCnNldC5zZWVkKDcpDQojIGxvYWQgdGhlIGxpYnJhcnkNCmxpYnJhcnkobWxiZW5jaCkNCiMgcmYgY29udHJvbA0KY29udHJvbCA8LSByZmVDb250cm9sKGZ1bmN0aW9ucz1yZkZ1bmNzLCBtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKQ0KIyBydW4gdGhlIFJGRSBhbGdvcml0aG0gYWdhaW5zdCBvbmUgb2YgdGhlIHRhcmdldCB2YXJpYWJsZXMgb25seQ0KcmVzdWx0cyA8LSByZmUoZGZjcF9taVssMTozMF0sZGZjcF9taVssMzRdLCBzaXplcz1jKDE6MzApLCByZmVDb250cm9sPWNvbnRyb2wpDQpwcmludChyZXN1bHRzKQ0KIyBsaXN0IGZlYXR1cmVzDQpwcmVkaWN0b3JzKHJlc3VsdHMpDQpgYGANClRoZSB2YXJpYWJsZSBzdF9kc190aW1lX3NpbmNlX2xhc3RfZGlhZ25vc2lzIHdhcyBzbGlnaHRseSBsb3dlciBpbiBpbXBvcnRhbmNlIHRoYW4gdGhlIG9uZSBpdCBoYXMgaGlnaCBjb3JyZWxhdGlvbiB3aXRoLCBzbyB0aGlzIG9uZSB3aWxsIGJlIHJlbW92ZWQuDQoNCmBgYHtyfQ0KI3JlbW92ZSByZWR1bmRhbnQgZmVhdHVyZSBmcm9tIGFsbCBkYXRhc2V0cw0KDQpkZmNwX21pIDwtIGRmY3BfbWkgJT4lIHNlbGVjdCgtc3RfZHNfdGltZV9zaW5jZV9sYXN0X2RpYWdub3NpcykNCmRmY3BfY2NhIDwtIGRmY3BfY2NhICU+JSBzZWxlY3QoLXN0X2RzX3RpbWVfc2luY2VfbGFzdF9kaWFnbm9zaXMpDQpkZmNwX21pX2QyIDwtIGRmY3BfbWlfZDIgJT4lIHNlbGVjdCgtc3RfZHNfdGltZV9zaW5jZV9sYXN0X2RpYWdub3NpcykNCmRmY3BfbWlfZDMgPC0gZGZjcF9taV9kMyAlPiUgc2VsZWN0KC1zdF9kc190aW1lX3NpbmNlX2xhc3RfZGlhZ25vc2lzKQ0KZGZjcF9taV9kNCA8LSBkZmNwX21pX2Q0ICU+JSBzZWxlY3QoLXN0X2RzX3RpbWVfc2luY2VfbGFzdF9kaWFnbm9zaXMpDQpkZmNwX21pX2Q1IDwtIGRmY3BfbWlfZDUgJT4lIHNlbGVjdCgtc3RfZHNfdGltZV9zaW5jZV9sYXN0X2RpYWdub3NpcykNCmBgYA0KDQojIyBPcHRpb25hbCBzdGVwOiBUcmFuc2Zvcm0gZGF0YQ0KU2NhbGluZyBkYXRhIGFsbG93cyBtb2RlbHMgdG8gY29tcGFyZSByZWxhdGl2ZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gZGF0YSBwb2ludHMgbW9yZSBlZmZlY3RpdmVseS4NCg0KYGBge3J9DQojc2NhbGUgdGhlIG51bWVyaWMgY29sdW1ucyBpbiBhbGwgdGhlIGRhdGFzZXRzIHRvIGJlIHVzZWQgaW4gdGhlIHRlc3QgbW9kZWxzDQoNCmRmY3BfbWkgPC0gZGZjcF9taSAlPiUgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgc2NhbGUpKQ0KZGZjcF9jY2EgPC0gZGZjcF9jY2EgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIHNjYWxlKSkNCmRmY3BfbWlfZDUgPC0gZGZjcF9taV9kNSAlPiUgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgc2NhbGUpKQ0KDQpgYGANCg0KIyBTdGVwIDk6IEJ1aWxkIGFuZCBUZXN0IE1vZGVscw0KDQojIyBDcmVhdGUgVHJhaW5pbmcgYW5kIFRlc3QgU2V0cw0KU2VlZHMgc2V0IGZvciByZXByb2R1Y2liaWxpdHkuDQoNCmBgYHtyfQ0KI21pIGRhdGENCnNldC5zZWVkKDEyMykNCmluZGV4IDwtIHNhbXBsZSgyLCBucm93KGRmY3BfbWkpLA0KICAgICAgICAgICAgICByZXBsYWNlID0gVFJVRSwNCiAgICAgICAgICAgICAgcHJvYiA9IGMoMC43LCAwLjMpKQ0KdHJhaW5fbWkgPC0gZGZjcF9taVtpbmRleD09MSxdDQp0ZXN0X21pIDwtIGRmY3BfbWlbaW5kZXg9PTIsXSANCg0KI2NjYSBkYXRhDQpzZXQuc2VlZCgxMjMpDQppbmRleDEgPC0gc2FtcGxlKDIsIG5yb3coZGZjcF9jY2EpLA0KICAgICAgICAgICAgICAgICByZXBsYWNlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgcHJvYiA9IGMoMC43LCAwLjMpKQ0KdHJhaW5fY2NhIDwtIGRmY3BfY2NhW2luZGV4MT09MSxdIA0KdGVzdF9jY2EgPC0gZGZjcF9jY2FbaW5kZXgxPT0yLF0gDQoNCiNkZWx0YTUgZGF0YQ0Kc2V0LnNlZWQoMTIzKQ0KaW5kZXg1IDwtIHNhbXBsZSgyLCBucm93KGRmY3BfbWlfZDUpLA0KICAgICAgICAgICAgICAgICByZXBsYWNlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgcHJvYiA9IGMoMC43LCAwLjMpKQ0KdHJhaW5fZDUgPC0gZGZjcF9taV9kNVtpbmRleDU9PTEsXSANCnRlc3RfZDUgPC0gZGZjcF9taV9kNVtpbmRleDU9PTIsXSANCmBgYA0KDQoNCiMjIFByZWRpY3QgQWdhaW5zdCBUZXN0IERhdGENClN0YXJ0IHdpdGggYmFzZWxpbmUgbW9kZWwgdXNpbmcgbXVsdGlwbGUgaW1wdXRhdGlvbi4gUmFuZG9tIEZvcmVzdCBjbGFzc2lmaWNhdGlvbiBmb3IgZWFjaCBkZXBlbmRlbnQgdmFyaWFibGUuIA0KDQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KI0NyZWF0ZSB4IGFuZCB5IHZhbHVlcw0KI2RmY3BfbWkgZGF0YQ0KeCA8LSB0cmFpbl9taVssLTMzXSAjcmVtb3ZlIGRlcGVuZGVudCB2YXJpYWJsZSANCnkgPC0gYXMuZmFjdG9yKHRyYWluX21pJGJpb3BzeSkgDQpzZXQuc2VlZCgzNDUpICNmb3IgcmVwcm9kdWNpYmlsaXR5DQoNCiNGaXQgbW9kZWwgd2l0aCB0cmFpbmluZyBkYXRhIGFuZCByZXZpZXcgZGV0YWlscw0KcmYgPSByYW5kb21Gb3Jlc3QoeCA9IHgsDQogICAgICAgICAgICAgICAgICB5ID0geSwNCiAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKQ0KcmYNCg0KI3ByZWRpY3QgcmVzdWx0cw0KeHQgPC0gdGVzdF9taVssLTMzXSAjcmVtb3ZlIGRlcGVuZGVudCB2YXJpYWJsZSBsaWtlIGNsYXNzDQp5dCA8LWFzLmZhY3Rvcih0ZXN0X21pJGJpb3BzeSkgI2tlZXAgY2xhc3Mgb25seQ0KcHJlZF95dCA8LSBwcmVkaWN0KHJmLCBuZXdkYXRhPXh0KQ0KDQojY2hlY2sgYWNjdXJhY3kNCmNvbmZ1c2lvbk1hdHJpeChwcmVkX3l0LCB5dCwgbW9kZT0iZXZlcnl0aGluZyIpDQoNCnZhckltcFBsb3QocmYpDQpgYGANCg0KQ0NBIGRhdGEgbW9kZWwuDQoNCmBgYHtyfQ0KI0NyZWF0ZSB4IGFuZCB5IHZhbHVlcw0KI2RmY3BfY2NhIGRhdGENCngxIDwtIHRyYWluX2NjYVssLTMzXQ0KeTEgPC0gYXMuZmFjdG9yKHRyYWluX2NjYSRiaW9wc3kpDQpzZXQuc2VlZCgzNDUpICNmb3IgcmVwcm9kdWNpYmlsaXR5DQoNCiNGaXQgbW9kZWwgd2l0aCB0cmFpbmluZyBkYXRhIGFuZCByZXZpZXcgZGV0YWlscw0KcmYxID0gcmFuZG9tRm9yZXN0KHggPSB4MSwNCiAgICAgICAgICAgICAgICAgIHkgPSB5MSwNCiAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKQ0KcmYxDQoNCiNwcmVkaWN0IHJlc3VsdHMNCngxdCA8LSB0ZXN0X2NjYVssLTMzXSAjcmVtb3ZlIGRlcGVuZGVudCB2YXJpYWJsZSBsaWtlIGNsYXNzDQp5MXQgPC1hcy5mYWN0b3IodGVzdF9jY2EkYmlvcHN5KSAja2VlcCBjbGFzcyBvbmx5DQpwcmVkX3kxdCA8LSBwcmVkaWN0KHJmMSwgbmV3ZGF0YT14MXQpDQoNCiNjaGVjayBhY2N1cmFjeQ0KY29uZnVzaW9uTWF0cml4KHByZWRfeTF0LCB5MXQsIG1vZGU9ImV2ZXJ5dGhpbmciKQ0KDQp2YXJJbXBQbG90KHJmMSkNCg0KYGBgDQpUaGUgVmFyaWFibGUgSW1wb3J0YW5jZSBwbG90cyBhcmUgdmVyeSBkaWZmZXJlbnQgYWNyb3NzIHRoZSB0d28gbW9kZWxzLCBkdWUgdG8gdGhlIGRhdGEgdGhhdCBpcyBsb3N0IHRocm91Z2ggQ0NBLg0KDQojIyMgV2hpY2ggTW9kZWwgUHJvZHVjZWQgdGhlIEJlc3QgUmVzdWx0cz8NClRoZSBmb2xsb3dpbmcgY29kZSBibG9jayBjYXB0dXJlcyB0aGUgbW9kZWwgd2hpY2ggcHJvZHVjZWQgdGhlIGJlc3QgcmVzdWx0cywgYmFzZWQgb24gS2FwcGEgYW5kIGNvcmUgbWV0cmljIG9mIGJhbGFuY2VkIGFjY3VyYWN5Lg0KYGBge3J9DQojIHNldCBtb2RlbCB2YXJpYWJsZSBmb3IgYmVzdCBwZXJmb3JtYW5jZSBhcyBNSSBvciBDQ0ENCmJldHRlcl9tb2RlbCA9ICJNSSINCg0KaWYoYmV0dGVyX21vZGVsPT0iTUkiIHx8IGJldHRlcl9tb2RlbD09IkNDQSIpIHtwcmludChwYXN0ZShiZXR0ZXJfbW9kZWwsICJwcm9kdWNlZCB0aGUgYmVzdCByZXN1bHQgaW4gdGhpcyBkYXRhIGV4cGVyaW1lbnQgdGVzdC4iKSkNCn1lbHNlew0KICAgIHByaW50KCJQbGVhc2UgZW50ZXIgZWl0aGVyICdNSScgb3IgJ0NDQScgYXMgdGhlIHZhcmlhYmxlIHZhbHVlIGZvciBiZXR0ZXJfbW9kZWwuIil9DQpgYGANCg0KDQoNCiMjIyBEb2VzIEhpZ2hlc3QgRGVsdGEgQWRqdXN0bWVudCBIYXZlIEJpZyBJbXBhY3Qgb24gUmVzdWx0cz8NCkNvZGUgYmxvY2sgaW5jbHVkZWQgdG8gZWRpdCBmb3IgZGVsdGEgbW9kZWwuDQoNCmBgYHtyfQ0KI0NyZWF0ZSB4IGFuZCB5IHZhbHVlcw0KI2RmY3BfY2NhIGRhdGENCng1IDwtIHRyYWluX2Q1WywtMzNdDQp5NSA8LSBhcy5mYWN0b3IodHJhaW5fZDUkYmlvcHN5KQ0Kc2V0LnNlZWQoMzQ1KSAjZm9yIHJlcHJvZHVjaWJpbGl0eQ0KDQojRml0IG1vZGVsIHdpdGggdHJhaW5pbmcgZGF0YSBhbmQgcmV2aWV3IGRldGFpbHMNCnJmNSA9IHJhbmRvbUZvcmVzdCh4ID0geDUsDQogICAgICAgICAgICAgICAgICB5ID0geTUsDQogICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkNCnJmNQ0KDQojdGVzdCB3aXRoIHRlc3QgZGF0YQ0KeDV0IDwtIHRlc3RfZDVbLC0zM10NCnk1dCA8LWFzLmZhY3Rvcih0ZXN0X2Q1JGJpb3BzeSkNCnByZWRfeTV0IDwtIHByZWRpY3QocmY1LCBuZXdkYXRhPXg1dCkNCg0KI2NoZWNrIGFjY3VyYWN5DQpjb25mdXNpb25NYXRyaXgocHJlZF95NXQsIHk1dCwgbW9kZT0iZXZlcnl0aGluZyIpDQoNCnZhckltcFBsb3QocmY1KQ0KYGBgDQoNClRoZSBpbXBhY3QgcmVzdWx0IGlzIGNhcHR1cmVkIGFzIGEgWWVzIG9yIE5vIHJlc3BvbnNlIGluIHRoZSBjb2RlIGJlbG93LiBUaGUgS2FwcGEgaXMgbG93ZXIgYnV0IHRoZSBCYWxhbmNlZCBBY2N1cmFjeSBpcyBoaWdoZXIgYXMgc3BlY2lmaWNpdHkgaXMgaW1wcm92ZWQuIFRoZXJlIGhhcyBiZWVuIGEgYmlnIGltcGFjdCBvbiByZXN1bHRzLg0KDQpgYGB7cn0NCiNJcyB0aGVyZSBhIGJpZyBpbXBhY3Qgb24gcmVzdWx0cz8NCmRlbHRhX2ltcGFjdCA9ICdZZXMnDQoNCmlmKGRlbHRhX2ltcGFjdD09IlllcyIgfHwgZGVsdGFfaW1wYWN0PT0iTm8iKSB7cHJpbnQocGFzdGUoZGVsdGFfaW1wYWN0LCAiaXMgdmFyaWFibGUgdmFsdWUgZm9yIGRlbHRhX2ltcGFjdC4iKSkNCn1lbHNlew0KICAgIHByaW50KCJQbGVhc2UgZW50ZXIgZWl0aGVyICdZZXMnIG9yICdObycgYXMgdGhlIHZhcmlhYmxlIHZhbHVlIGZvciBkZWx0YV9pbXBhY3QuIil9DQoNCmBgYA0KDQojIFN1bW1hcnkgYW5kIFJlY29tbWVuZGF0aW9ucw0KVGhlIGZvbGxvd2luZyBjb2RlIHdpbGwgcHJpbnQgYSBzdW1tYXJ5LCBiYXNlZCBvbiB0aGUgdmFyaWFibGUgaW5mb3JtYXRpb24gcmVjb3JkZWQgaW4gZWFybGllciBjb2RlIGJsb2Nrcy4gDQoNCiMjIyBWYXJpYWJsZSBQYXJhbWV0ZXJzIFNldCBJbiBFeHBlcmltZW50DQpgYGB7cn0NCnByaW50KHBhc3RlKG1pc3NpbmduZXNzLCAiaXMgbWlzc2luZyBkYXRhIGxldmVsLiIpKQ0KcHJpbnQocGFzdGUoIklzIGRhdGEgbW9yZSBtb3JlIGxpa2VseSB0byBiZSBtaXNzaW5nIGluIHNvbWUgdmFyaWFibGVzIHRoYW4gb3RoZXJzPyIsIGxpa2VsaWhvb2QpKQ0KcHJpbnQocGFzdGUoIklzIGRhdGEgbWlzc2luZyBmcm9tIGRlcGVuZGVudCB2YXJpYWJsZShzKSBvbmx5PyIsIGRlcGVuZGVudF9vbmx5KSkNCnByaW50KHBhc3RlKCJEb2VzIGhpZ2hlciBkZWx0YSBhZGp1c3RtZW50IGhhdmUgYmlnIGltcGFjdCBvbiByZXN1bHRzPyIsIGRlbHRhX2ltcGFjdCkpDQpgYGANCg0KDQojIyMgSXMgRGF0YSBNQ0FSIG9yIE1BUi9NTkFSPw0KYGBge3J9DQoNCmlmIChsaWtlbGlob29kPT0iTm8iICYgbWNhcl9wcmVzdWx0PT0iZXJyb3IiKSB7DQogICBoeXBvdGhlc2lzPSJNaXNzaW5nIGRhdGEgcGF0dGVybiBwcm92aWRlcyBzb21lIHN1cHBvcnQgZm9yIE1DQVIgaHlwb3RoZXNpcy4gSG93ZXZlciwgbm8gcmVzdWx0IGF2YWlsYWJsZSBmb3IgTUNBUiBUZXN0LiINCiAgIH0gZWxzZSBpZiAobGlrZWxpaG9vZD09Ik5vIiAmIG1jYXJfcHJlc3VsdDwwLjA1KSB7DQogICBoeXBvdGhlc2lzPSJIeXBvdGhlc2lzIHVucHJvdmVuLiBNQ0FSIFRlc3QgYW5kIG1pc3NpbmcgZGF0YSBwYXR0ZXJuIGluZGljYXRlIGRpZmZlcmVudCBtaXNzaW5nIGRhdGEgbWVjaGFuaXNtcy4iDQogICB9IGVsc2UgaWYgKGxpa2VsaWhvb2Q9PSJObyIgJiBtY2FyX3ByZXN1bHQ+PTAuMDUpIHsNCiAgICAgaHlwb3RoZXNpcz0iTUNBUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCBieSBNQ0FSIFRlc3QgYW5kIG1pc3NpbmcgZGF0YSBwYXR0ZXJuLiINCiAgIH0gZWxzZSBpZihsaWtlbGlob29kPT0iWWVzIiAmIG1jYXJfcHJlc3VsdD09ImVycm9yIikgew0KICAgICBoeXBvdGhlc2lzPSJNaXNzaW5nIGRhdGEgcGF0dGVybiBwcm92aWRlcyBzb21lIHN1cHBvcnQgZm9yIE1BUi9NTkFSIGh5cG90aGVzaXMuIEhvd2V2ZXIsIG5vIHJlc3VsdCBhdmFpbGFibGUgZm9yIE1DQVIgVGVzdC4iDQogICB9IGVsc2UgaWYgKGxpa2VsaWhvb2Q9PSJZZXMiICYgbWNhcl9wcmVzdWx0PDAuMDUpIHsNCiAgICAgaHlwb3RoZXNpcz0iTUFSL01OQVIgaHlwb3RoZXNpcyBzdXBwb3J0ZWQgYnkgTUNBUiBUZXN0IGFuZCBtaXNzaW5nIGRhdGEgcGF0dGVybi4iDQogICB9ZWxzZSBpZiAobGlrZWxpaG9vZD09IlllcyIgJiBtY2FyX3ByZXN1bHQ+PTAuMDUpIHsNCiAgICAgaHlwb3RoZXNpcz0iSHlwb3RoZXNpcyB1bnByb3Zlbi4gTUNBUiBUZXN0IGFuZCBtaXNzaW5nIGRhdGEgcGF0dGVybiBpbmRpY2F0ZSBkaWZmZXJlbnQgbWlzc2luZyBkYXRhIG1lY2hhbmlzbXMuIg0KICAgfWVsc2Ugew0KICAgcHJpbnQoIk5vIGh5cG90aGVzaXMgZm91bmQuIikNCn0NCg0KcHJpbnQoaHlwb3RoZXNpcykNCmBgYA0KDQojIyMgUmVjb21tZW5kZWQgQXBwcm9hY2ggZm9yIE1pc3NpbmcgRGF0YQ0KYGBge3J9DQojZGVmaW5lIGRpZmZlcmVudCByZWNvbW1lbmRlZCBhcHByb2FjaGVzDQpkYXRhX2FwcHJvYWNoMSA9ICJEdWUgdG8gb3ZlcmFsbCBsZXZlbCBvZiBtaXNzaW5nIGRhdGEgKG9yIGxldmVsIG9mIG1pc3NpbmcgZGF0YSBmcm9tIGRlcGVuZGVudCB2YXJpYWJsZSBvbmx5KSB0aGVyZSBpcyBhIHJpc2sgdGhhdCBhIHBvb3IgbW9kZWwgd2lsbCBiZSByZXR1cm5lZCwgcmVnYXJkbGVzcyBvZiBtZXRob2QgdXNlZCwgdGhhdCB3b3VsZCBub3QgZ2VuZXJhbGlzZSB3ZWxsIG9uIG5ldyBkYXRhLiINCg0KZGF0YV9hcHByb2FjaDIgPSAiQXMgTUNBUiBoeXBvdGhlc2lzIGlzIHN1cHBvcnRlZCBhbmQgbWlzc2luZyBkYXRhIGlzIGxlc3MgdGhhbiAxMCUsIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgaXMgbGlrZWx5IHRvIHByb2R1Y2UgdW5iaWFzZWQgcmVzdWx0cy4gSG93ZXZlciwgbXVsdGlwbGUgaW1wdXRhdGlvbiBtYXkgcHJvZHVjZSBhIG1vcmUgZWZmZWN0aXZlIG1vZGVsIGluIHNvbWUgY2lyY3Vtc3RhbmNlcy4iDQoNCmRhdGFfYXBwcm9hY2gzID0gIk1DQVIgaHlwb3RoZXNpcyBpcyBzdXBwb3J0ZWQuIEhvd2V2ZXIsIGFzIG1pc3NpbmcgZGF0YSBpcyBiZXR3ZWVuIDEwLTUwJSwgbXVsdGlwbGUgaW1wdXRhdGlvbiBpcyBsaWtlbHkgdG8gcHJvZHVjZSBhIG1vcmUgZWZmZWN0aXZlIG1vZGVsLiINCg0KZGF0YV9hcHByb2FjaDQgPSAiTUFSL01OQVIgaHlwb3RoZXNpcyBzdXBwb3J0ZWQsIG9yIE1DQVIgaHlwb3RoZXNpcyB1bnByb3Zlbi4gSG93ZXZlciwgYXMgbWlzc2luZyBkYXRhIGlzIGxlc3MgdGhhbiAxMCUgYW5kIG1pc3NpbmcgZnJvbSB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIG9ubHksIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgbWF5IGJlIHRoZSBtb3JlIHJlbGlhYmxlIGFwcHJvYWNoLiINCg0KZGF0YV9hcHByb2FjaDUgPSAiTUFSL01OQVIgaHlwb3RoZXNpcyBzdXBwb3J0ZWQsIG9yIE1DQVIgaHlwb3RoZXNpcyB1bnByb3Zlbi4gQWxzbywgYXMgbWlzc2luZyBkYXRhIGlzIGxlc3MgdGhhbiA1MCUgYW5kIHRoZSBkZWx0YSBzZW5zaXRpdml0eSBhbmFseXNpcyBzdWdnZXN0cyB0aGUgcmVzdWx0cyBhcmUgcmVsYXRpdmVseSBzdGFibGUsIG11bHRpcGxlIGltcHV0YXRpb24gaXMgdGhlIHJlY29tbWVuZGVkIGFwcHJvYWNoLiINCg0KZGF0YV9hcHByb2FjaDYgPSAiTUFSL01OQVIgaHlwb3RoZXNpcyBzdXBwb3J0ZWQsIG9yIE1DQVIgaHlwb3RoZXNpcyB1bnByb3Zlbi4gVGhlIGRlbHRhIHNlbnNpdGl2aXR5IGFuYWx5c2lzIHN1Z2dlc3RzIHRoZSByZXN1bHRzIGFyZSBub3Qgc3RhYmxlIGFuZCBpbmRpY2F0aXZlIG9mIE1OQVIgZGF0YS4gVGhlcmVmb3JlLCBhIHBhdHRlcm4gbWl4dHVyZSBtb2RlbCBpcyByZWNvbW1lbmRlZCBmb3IgZnVydGhlciBjb25zaWRlcmF0aW9uLiBUaGVyZSBhcmUgYSBjb3VwbGUgb2YgUiBtaWNlIHBhY2thZ2UgZnVuY3Rpb25zIHdvcnRoIGV4cGxvcmluZyBmdXJ0aGVyIGZvciB0aGlzOiBtaWNlLmltcHV0ZS5yaSBhbmQgbWljZS5pbXB1dGUubW5hci5sb2dyZWcgKHZhbiBCdXVyZW4gZXQgYWwuLCAyMDIzKS4iDQoNCiNkZWZpbmUgY29uZGl0aW9uYWwgc3RhdGVtZW50cw0KaWYgKG1pc3NpbmduZXNzPj0wLjUpIHsNCiAgIGFwcHJvYWNoPWRhdGFfYXBwcm9hY2gxDQogICB9IGVsc2UgaWYgKG1pc3NpbmduZXNzPjAuMSAmIGRlcGVuZGVudF9vbmx5PT0nWWVzJykgew0KICAgICBhcHByb2FjaD1kYXRhX2FwcHJvYWNoMQ0KICAgfSBlbHNlIGlmIChoeXBvdGhlc2lzPT0iTUNBUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCBieSBNQ0FSIFRlc3QgYW5kIG1pc3NpbmcgZGF0YSBwYXR0ZXJuLiIgJiBtaXNzaW5nbmVzczw9MC4xKSB7DQogICAgICAgYXBwcm9hY2g9ZGF0YV9hcHByb2FjaDIgDQogICB9IGVsc2UgaWYgKGh5cG90aGVzaXM9PSJNQ0FSIGh5cG90aGVzaXMgc3VwcG9ydGVkIGJ5IE1DQVIgVGVzdCBhbmQgbWlzc2luZyBkYXRhIHBhdHRlcm4uIiAmIG1pc3NpbmduZXNzPjAuMSAmIG1pc3NpbmduZXNzPDAuNSkgew0KICAgICBhcHByb2FjaD1kYXRhX2FwcHJvYWNoMyANCiAgIH0gZWxzZSBpZihoeXBvdGhlc2lzIT0iTUNBUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCBieSBNQ0FSIFRlc3QgYW5kIG1pc3NpbmcgZGF0YSBwYXR0ZXJuLiIgJiBtaXNzaW5nbmVzczw9MC4xICYgZGVwZW5kZW50X29ubHk9PSJZZXMiKSB7DQogICAgIGFwcHJvYWNoPWRhdGFfYXBwcm9hY2g0DQogICB9IGVsc2UgaWYoaHlwb3RoZXNpcyE9Ik1DQVIgaHlwb3RoZXNpcyBzdXBwb3J0ZWQgYnkgTUNBUiBUZXN0IGFuZCBtaXNzaW5nIGRhdGEgcGF0dGVybi4iICYgbWlzc2luZ25lc3M8PTAuNSAmIGRlbHRhX2ltcGFjdD09Ik5vIikgew0KICAgICBhcHByb2FjaD1kYXRhX2FwcHJvYWNoNQ0KICAgfSBlbHNlIGlmKGh5cG90aGVzaXMhPSJNQ0FSIGh5cG90aGVzaXMgc3VwcG9ydGVkIGJ5IE1DQVIgVGVzdCBhbmQgbWlzc2luZyBkYXRhIHBhdHRlcm4uIiAgJiBkZWx0YV9pbXBhY3Q9PSJZZXMiKSB7DQogICAgIGFwcHJvYWNoPWRhdGFfYXBwcm9hY2g2DQogICB9ZWxzZSB7DQogICBwcmludCgiTm8gYXBwcm9hY2ggZm91bmQuIikNCn0NCg0KcHJpbnQoYXBwcm9hY2gpDQoNCmBgYA0KDQojIyMgQ29uc2lkZXJhdGlvbiBvZiBUZXN0IE1vZGVsIFJlc3VsdHMgaW4gRGF0YSBFeHBlcmltZW50DQpgYGB7cn0NCg0KaWYgKGJldHRlcl9tb2RlbD09Ik1JIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoMSkgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBNdWx0aXBsZSBJbXB1dGF0aW9uIHByb2R1Y2VkIGEgbW9yZSBlZmZlY3RpdmUgbW9kZWwgdGhhbiBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzLiBIb3dldmVyLCB0aGVyZSBpcyBhIHJpc2sgdGhhdCBpdCB3b3VsZCBub3QgZ2VuZXJhbGlzZSB3ZWxsIG9uIG5ldyBkYXRhLiIpDQogICB9IGVsc2UgaWYgKGJldHRlcl9tb2RlbD09IkNDQSIgJiBhcHByb2FjaD09ZGF0YV9hcHByb2FjaDEpIHsNCiAgIHByaW50KCJGb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEgZXhwZXJpbWVudCwgY29tcGxldGUgY2FzZSBhbmFseXNpcyBwcm9kdWNlZCB0aGUgbW9zdCBlZmZlY3RpdmUgbW9kZWwuIEhvd2V2ZXIsIHRoZXJlIGlzIGEgcmlzayB0aGF0IGl0IHdvdWxkIG5vdCBnZW5lcmFsaXNlIHdlbGwgb24gbmV3IGRhdGEuIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iTUkiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2gyKSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIE11bHRpcGxlIEltcHV0YXRpb24gcHJvZHVjZWQgYSBtb3JlIGVmZmVjdGl2ZSBtb2RlbCB0aGFuIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMuIEhvd2V2ZXIsIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgd291bGQgYmUgbGlrZWx5IHRvIHByb2R1Y2UgdW5iaWFzZWQgcmVzdWx0cyBhbHNvLiIpDQogICB9IGVsc2UgaWYgKGJldHRlcl9tb2RlbD09IkNDQSIgJiBhcHByb2FjaD09ZGF0YV9hcHByb2FjaDIpIHsNCiAgIHByaW50KCJGb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEgZXhwZXJpbWVudCwgY29tcGxldGUgY2FzZSBhbmFseXNpcyBwcm9kdWNlZCB0aGUgbW9zdCBlZmZlY3RpdmUgbW9kZWwuIEhvd2V2ZXIsIG11bHRpcGxlIGltcHV0YXRpb24gbWF5IHByb2R1Y2UgYSBtb3JlIGVmZmVjdGl2ZSBtb2RlbCBpbiBzb21lIGNpcmN1bXN0YW5jZXMuIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iTUkiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2gzKSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIE11bHRpcGxlIEltcHV0YXRpb24gcHJvZHVjZWQgYSBtb3JlIGVmZmVjdGl2ZSBtb2RlbCB0aGFuIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMuIFRoaXMgcmVzdWx0IGlzIGV4cGVjdGVkLCBnaXZlbiB2YXJpYWJsZXMgcHJvdmlkZWQuIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iQ0NBIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoMykgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzIHByb2R1Y2VkIHRoZSBtb3N0IGVmZmVjdGl2ZSBtb2RlbC4gSG93ZXZlciwgY2F1dGlvbiBzaG91bGQgYmUgbm90ZWQgb3ZlciByZXN1bHRzIGR1ZSB0byBoaWdoIGxldmVsIG9mIG1pc3NpbmduZXNzLiIpDQogICB9IGVsc2UgaWYgKGJldHRlcl9tb2RlbD09Ik1JIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoNCkgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBNdWx0aXBsZSBJbXB1dGF0aW9uIHByb2R1Y2VkIGEgbW9yZSBlZmZlY3RpdmUgbW9kZWwgdGhhbiBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzLiBIb3dldmVyLCBjYXV0aW9uIHNob3VsZCBiZSB0YWtlbiB3aXRoIGhvdyBtaXNzaW5nIGRhdGEgaW4gZGVwZW5kZW50IHZhcmlhYmxlcyBpcyBoYW5kbGVkLCBhcyBDQ0EgbWF5IGJlIG1vcmUgcmVsaWFibGUuIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iQ0NBIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoNCkgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzIHByb2R1Y2VkIHRoZSBtb3N0IGVmZmVjdGl2ZSBtb2RlbC4gVGhpcyBzaG91bGQgYmUgYSByZWxpYWJsZSBhcHByb2FjaCBmb3IgdGhpcyBtaXNzaW5nIGRhdGEgcHJvYmxlbS4iKQ0KICAgfSBlbHNlIGlmIChiZXR0ZXJfbW9kZWw9PSJNSSIgJiBhcHByb2FjaD09ZGF0YV9hcHByb2FjaDUpIHsNCiAgIHByaW50KCJGb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEgZXhwZXJpbWVudCwgTXVsdGlwbGUgSW1wdXRhdGlvbiBwcm9kdWNlZCBhIG1vcmUgZWZmZWN0aXZlIG1vZGVsIHRoYW4gY29tcGxldGUgY2FzZSBhbmFseXNpcy4gVGhpcyBzaG91bGQgYmUgYSByZWxpYWJsZSBhcHByb2FjaCBmb3IgdGhpcyBtaXNzaW5nIGRhdGEgcHJvYmxlbS4iKQ0KICAgfSBlbHNlIGlmIChiZXR0ZXJfbW9kZWw9PSJDQ0EiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2g1KSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgcHJvZHVjZWQgdGhlIG1vc3QgZWZmZWN0aXZlIG1vZGVsLiBIb3dldmVyLCB0aGUgTUNBUiBoeXBvdGhlc2lzIGlzIGVpdGhlciBub3Qgc3VwcG9ydGVkIG9yIHVuY2xlYXIgc28gY2F1dGlvbiBpcyBhZHZpc2VkIG9uIHRoZSByZXN1bHRzIHByb2R1Y2VkLiIpDQogICB9IGVsc2UgaWYgKGJldHRlcl9tb2RlbD09Ik1JIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoNikgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBNdWx0aXBsZSBJbXB1dGF0aW9uIHByb2R1Y2VkIGEgbW9yZSBlZmZlY3RpdmUgbW9kZWwgdGhhbiBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzLiBIb3dldmVyLCBpdCBpcyByZWNvbW1lbmRlZCB0aGF0IGEgcGF0dGVybiBtaXh0dXJlIG1vZGVsIGlzIGNvbnNpZGVyZWQgYWxzby4iKQ0KICAgfSBlbHNlIGlmIChiZXR0ZXJfbW9kZWw9PSJDQ0EiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2g2KSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgcHJvZHVjZWQgdGhlIG1vc3QgZWZmZWN0aXZlIG1vZGVsLiBIb3dldmVyLCBpdCBpcyByZWNvbW1lbmRlZCB0aGF0IGEgcGF0dGVybiBtaXh0dXJlIG1vZGVsIGlzIGNvbnNpZGVyZWQgYWxzby4iKQ0KICAgfSBlbHNlIHsNCiAgIHByaW50KCJObyBtb2RlbCBldmFsdWF0aW9uIGZvdW5kLiIpDQp9DQpgYGANCg0KDQoNCiMgUmVmZXJlbmNlcw0KLSB2YW4gQnV1cmVuLCBTLiBldCBhbC4gKDIwMjMpIOKAmG1pY2U6IE11bHRpdmFyaWF0ZSBJbXB1dGF0aW9uIGJ5IENoYWluZWQgRXF1YXRpb25z4oCZLiBDUkFOLiBBdmFpbGFibGUgYXQ6IGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9bWljZSAoQWNjZXNzZWQ6IDEwIEF1Z3VzdCAyMDIzKS4NCi0gRmVybmFuZGVzLCBLLiwgQ2FyZG9zbywgSi4gYW5kIEZlcm5hbmRlcywgSi4gKDIwMTcpIENlcnZpY2FsIGNhbmNlciAoUmlzayBGYWN0b3JzKSwgVUNJIE1hY2hpbmUgTGVhcm5pbmcgUmVwb3NpdG9yeS4NCi0gU2luZGlhbmksIEFtZXIgTWFobW91ZCBldCBhbC4g4oCcSW52ZXN0aWdhdGluZyBDZXJ2aWNhbCBSaXNrIEZhY3RvcnMgdGhhdCBMZWFkIHRvIEN5dG9sb2dpY2FsIGFuZCBCaW9wc3kgRXhhbWluYXRpb24u4oCdIE1lZGljYWwgYXJjaGl2ZXMgKFNhcmFqZXZvLCBCb3NuaWEgYW5kIEhlcnplZ292aW5hKSB2b2wuIDc0LDQgKDIwMjApOiAyOTQtMjk3LiBkb2k6MTAuNTQ1NS9tZWRhcmguMjAyMC43NC4yOTQtMjk3DQotIFZpbmssIEcuIGFuZCB2YW4gQnV1cmVuLCBTLiAobm8gZGF0ZSkgbWljZTogQW4gYXBwcm9hY2ggdG8gc2Vuc2l0aXZpdHkgYW5hbHlzaXMsIHd3dy5nZXJrb3ZpbmsuY29tLiBBdmFpbGFibGUgYXQ6IGh0dHBzOi8vd3d3Lmdlcmtvdmluay5jb20vbWljZVZpZ25ldHRlcy9TZW5zaXRpdml0eV9hbmFseXNpcy9TZW5zaXRpdml0eV9hbmFseXNpcy5odG1sIChBY2Nlc3NlZDogNSBBdWd1c3QgMjAyMykuDQoNCg0KLS1MYXN0IHVwZGF0ZWQ6IEF1Z3VzdCAyMDIzICANCi0tRW5kLS0=