This is an exemplar workbook showing how the Vaar Notebook can be applied to UCI’s Breast Cancer Wisconsin Original Data (Wolberg, 1992) dataset 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. This is a binary classification problem to diagnose breast cancer malignancy, where the target diagnosis is the minority class.

library(data.table)
df <- fread('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data', header=FALSE, stringsAsFactors = FALSE)

 Downloaded 4021 bytes...
 Downloaded 12213 bytes...
 Downloaded 16309 bytes...
 Downloaded 19889 bytes...
head(df)
summary(df)
       V1                 V2               V3               V4               V5        
 Min.   :   61634   Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.000  
 1st Qu.:  870688   1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 1.000   1st Qu.: 1.000  
 Median : 1171710   Median : 4.000   Median : 1.000   Median : 1.000   Median : 1.000  
 Mean   : 1071704   Mean   : 4.418   Mean   : 3.134   Mean   : 3.207   Mean   : 2.807  
 3rd Qu.: 1238298   3rd Qu.: 6.000   3rd Qu.: 5.000   3rd Qu.: 5.000   3rd Qu.: 4.000  
 Max.   :13454352   Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.000  
       V6              V7                  V8               V9              V10        
 Min.   : 1.000   Length:699         Min.   : 1.000   Min.   : 1.000   Min.   : 1.000  
 1st Qu.: 2.000   Class :character   1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 1.000  
 Median : 2.000   Mode  :character   Median : 3.000   Median : 1.000   Median : 1.000  
 Mean   : 3.216                      Mean   : 3.438   Mean   : 2.867   Mean   : 1.589  
 3rd Qu.: 4.000                      3rd Qu.: 5.000   3rd Qu.: 4.000   3rd Qu.: 1.000  
 Max.   :10.000                      Max.   :10.000   Max.   :10.000   Max.   :10.000  
      V11      
 Min.   :2.00  
 1st Qu.:2.00  
 Median :2.00  
 Mean   :2.69  
 3rd Qu.:4.00  
 Max.   :4.00  
library(tidyverse) # to use pipes
#count obs per class
df %>%
  count(V11)

Note in summary of dataframe that V7 is the only character variable, the others are integers. There are 241 malignant and 458 benign observations in the data.

Step 2: Copy and Tidy Data

Tidyverse package used to rename variables.

dfcp <- df #copy data

#rename variables to something more meaningful
dfcp <- rename (dfcp, 
                        sampleID=V1, ClumpThickness=V2, CellSize=V3, CellShape=V4, Adhesion=V5, 
                        SingleESize=V6, BareNuclei=V7, BlandChromatin=V8, NormalNucleoli=V9, 
                        Mitoses=V10, Diagnosis=V11)

#Convert V7 variable to integer also
#This is now named BareNuclei
dfcp$BareNuclei <- as.integer(dfcp$BareNuclei)
Warning: NAs introduced by coercion

Check for Duplicates

Duplicates checked using n_distinct in tidyverse package

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

print(duplicates)

There are 691 unique records out of 699 observations(8 duplicates printed).

Remove duplicates and ID variable

dfcp <- dfcp %>% distinct() #691 obs remaining
dfcp <- select(dfcp,-c(sampleID)) #remove sampleID column 10 variables remain

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.

library(visdat)
library(ggplot2)
library(naniar)

vis_miss(dfcp)

miss_var_summary(dfcp)

ggplot(dfcp,
       aes (x = Diagnosis,
            y = BareNuclei,)) +
  geom_miss_point() +
  ggtitle ("Graph Showing Missing Data Pattern By Class Diagnosis")

ggplot
function (data = NULL, mapping = aes(), ..., environment = parent.frame()) 
{
    UseMethod("ggplot")
}
<bytecode: 0x0000024c1f8a0c08>
<environment: namespace:ggplot2>
mcar_test(dfcp)

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.

#set MCAR Test p.value as variable
mcar_presult = 0.2168686

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"

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"

What percentage of data is missing?

If it’s between 20-50% multiple imputation is likely to result in a more effective, unbiased model. The value is set as a variable in the following code.

#set missingness variable value
missingness = 0.02315485

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.

Visualise Correlation

Visualise correlation for numerical variables.

dfcp_x <- select_if(dfcp, is.numeric)
cor(dfcp_x)
               ClumpThickness  CellSize CellShape  Adhesion SingleESize BareNuclei BlandChromatin
ClumpThickness      1.0000000 0.6433396 0.6537521 0.4879492   0.5174478         NA      0.5610764
CellSize            0.6433396 1.0000000 0.9054195 0.7131170   0.7471112         NA      0.7595252
CellShape           0.6537521 0.9054195 1.0000000 0.6909890   0.7143934         NA      0.7384546
Adhesion            0.4879492 0.7131170 0.6909890 1.0000000   0.6084773         NA      0.6698127
SingleESize         0.5174478 0.7471112 0.7143934 0.6084773   1.0000000         NA      0.6205176
BareNuclei                 NA        NA        NA        NA          NA          1             NA
BlandChromatin      0.5610764 0.7595252 0.7384546 0.6698127   0.6205176         NA      1.0000000
NormalNucleoli      0.5357118 0.7272394 0.7246932 0.6024535   0.6340581         NA      0.6690593
Mitoses             0.3503536 0.4600643 0.4405924 0.4171673   0.4826440         NA      0.3438210
Diagnosis           0.7169385 0.8177198 0.8176933 0.7013708   0.6812326         NA      0.7566183
               NormalNucleoli   Mitoses Diagnosis
ClumpThickness      0.5357118 0.3503536 0.7169385
CellSize            0.7272394 0.4600643 0.8177198
CellShape           0.7246932 0.4405924 0.8176933
Adhesion            0.6024535 0.4171673 0.7013708
SingleESize         0.6340581 0.4826440 0.6812326
BareNuclei                 NA        NA        NA
BlandChromatin      0.6690593 0.3438210 0.7566183
NormalNucleoli      1.0000000 0.4276435 0.7155401
Mitoses             0.4276435 1.0000000 0.4241115
Diagnosis           0.7155401 0.4241115 1.0000000
vis_cor(dfcp_x)

There is a high level of correlation between variables and likely co-correlation between CellSize and CellShape for this dataset.

library(GGally) #ggplot2 extension for pairs matrix
#Change Diagnosis from integer to factor
dfcp$Diagnosis <- as.factor(dfcp$Diagnosis)

pm <- ggpairs(dfcp, columns = 1:10, ggplot2::aes(colour = Diagnosis), lower=list(combo=wrap("facethist",  
                                                                                                  binwidth=0.5)))
pm

Majority class - benign - has a lot more outliers and there is a high positive skew to the benign class also in the data. There is a moderate negative skew on some of the malignant class variables.

Calculate Skew

library(psych) #for skewness function
skew(dfcp_x)
 [1] 0.5889370 1.2278924 1.1586510 1.5010237 1.7115854 0.9912070 1.0979701 1.4013807 3.5291814
[10] 0.6533674

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 variable is at the top of the tree?

Adhesion is returned for this dataset. Variable value for missingness influencer set in code below.

#set value for missingness influencer
miss_influencer="Adhesion"
fxpt <- fluxplot(dfcp)

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

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 %>%
  filter(!is.na(BareNuclei))

library(mice)
init = mice(dfcp, maxit=0) 
meth = init$method
predM = init$predictorMatrix

#create imputed data
set.seed(123)
dfcp_imp = mice(dfcp, method=meth, predictorMatrix=predM, m=5)

 iter imp variable
  1   1  BareNuclei
  1   2  BareNuclei
  1   3  BareNuclei
  1   4  BareNuclei
  1   5  BareNuclei
  2   1  BareNuclei
  2   2  BareNuclei
  2   3  BareNuclei
  2   4  BareNuclei
  2   5  BareNuclei
  3   1  BareNuclei
  3   2  BareNuclei
  3   3  BareNuclei
  3   4  BareNuclei
  3   5  BareNuclei
  4   1  BareNuclei
  4   2  BareNuclei
  4   3  BareNuclei
  4   4  BareNuclei
  4   5  BareNuclei
  5   1  BareNuclei
  5   2  BareNuclei
  5   3  BareNuclei
  5   4  BareNuclei
  5   5  BareNuclei
#create dataset after imputation
dfcp_mi <- complete(dfcp_imp)

Check Imputation Visually

As warnings given, check imputation was possible.

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

vis_miss(dfcp_cca)

Step 7: Conduct MNAR Sensitivity Test

Use the variable 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, +1, +2, +3, +4)
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["BareNuclei"] <- 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,4))

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

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 and it has not really had an effect on the imputations. The blue line is fairly consistent between the two plots.

Create Complete Datasets with Delta Imputations 1 to 5

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

#review imputations
print(imp.d[[2]]$imp$BareNuclei)
print(imp.d[[3]]$imp$BareNuclei)
print(imp.d[[4]]$imp$BareNuclei)
print(imp.d[[5]]$imp$BareNuclei)
#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. The large number of outliers observed in the benign class in the pairs chart could be an accurate representation of the natural variability in data and diagnostic challenge.

Step 8: Select Features

This code is an example of how to determine which variables have above 0.9 correlation, using the caret package, as highly-correlated features do not generally improve models. Then, remove any variables identified from all datasets.

library(caret)
dfcp_mi_cor <- cor(dfcp_mi%>% select(-Diagnosis))
dfcp_mi <- dfcp_mi %>% select(-findCorrelation(dfcp_mi_cor, cutoff = 0.9))

#show column name difference between original data and selected features
setdiff(names(dfcp), names(dfcp_mi))
[1] "CellSize"

CellSize has been removed.

#remove CellSize from other datasets also

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

Optional step: Transform data

Scaling data allows models to compare relative relationships between data points more effectively. The Breast Cancer Wisconsin (Original) Data Set is already scaled (1 -10).

Step 9: Build and Test Models

This notebook uses a Support Vector Machine (SVM) Model. Other models are available.

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,] 


#delta2 data
set.seed(123)
index2 <- sample(2, nrow(dfcp_mi_d2),
                 replace = TRUE,
                 prob = c(0.7, 0.3))
train_d2 <- dfcp_mi_d2[index2==1,] 
test_d2 <- dfcp_mi_d2[index2==2,] 

#delta3 data
set.seed(123)
index3 <- sample(2, nrow(dfcp_mi_d3),
                 replace = TRUE,
                 prob = c(0.7, 0.3))
train_d3 <- dfcp_mi_d3[index3==1,] 
test_d3 <- dfcp_mi_d3[index3==2,] 

#delta4 data
set.seed(123)
index4 <- sample(2, nrow(dfcp_mi_d4),
                 replace = TRUE,
                 prob = c(0.7, 0.3))
train_d4 <- dfcp_mi_d4[index4==1,] 
test_d4 <- dfcp_mi_d4[index4==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,] 

Test SVM Kernel Approaches with Training Data

This code is based on the vignette from (Sallan, 2020) Comparing SVM kernels.

library(e1071) #using SVM model from e1071 package

Attaching package: ‘e1071’

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

    kurtosis, skewness
library(knitr) #to produce kable to visualise results

accuracy <- sapply(list(train_mi, train_cca, train_d2,
                        train_d3, train_d4, train_d5), 
                   
                   function(df){
                     
                     svm_models <- lapply(list("linear", "poly", "radial", "sigmoid"), function(meth){
                       return(svm(df[,-9], as.factor(df$Diagnosis), kernel=meth))
                     })
                     
                     names(svm_models) <- c("linear", "poly", "radial", "sigmoid")
                     
                     accuracy <- sapply(svm_models, function(x) sum(x$fitted == df$Diagnosis)/nrow(df))
                     names(accuracy) <- names(svm_models)
                     return(accuracy)
                     
                   })

accuracy <- data.frame(accuracy)
colnames(accuracy) <- c('train_mi', 'train_cca', 'train_d2',
                        'train_d3', 'train_d4', 'train_d5')

accuracy %>%
  kable(digits = 3)
train_mi train_cca train_d2 train_d3 train_d4 train_d5
linear 0.969 0.977 0.969 0.965 0.969 0.965
poly 0.969 0.972 0.967 0.971 0.971 0.969
radial 0.973 0.979 0.973 0.969 0.971 0.971
sigmoid 0.960 0.968 0.960 0.960 0.960 0.960

According to this test against training data, SVM Radial produces the best result.

Predict Against Test Data

Start with baseline model using multiple imputation.

#Create x and y values
#dfcp_mi data
x <- train_mi[,-9]
y <- as.factor(train_mi$Diagnosis)
set.seed(345) #for reproducibility

#Fit model with training data and review details
model_svm <- svm(x, y, kernel='radial')
print(model_svm)

Call:
svm.default(x = x, y = y, kernel = "radial")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 

Number of Support Vectors:  84
summary(model_svm)

Call:
svm.default(x = x, y = y, kernel = "radial")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 

Number of Support Vectors:  84

 ( 26 58 )


Number of Classes:  2 

Levels: 
 2 4
#test with test data
xt <- test_mi[,-9]
yt <-as.factor(test_mi$Diagnosis)
pred_yt <- predict(model_svm, xt)

#check accuracy
table(pred_yt, yt)
       yt
pred_yt   2   4
      2 135   1
      4   5  70
confusionMatrix(pred_yt, yt, mode="everything")
Confusion Matrix and Statistics

          Reference
Prediction   2   4
         2 135   1
         4   5  70
                                          
               Accuracy : 0.9716          
                 95% CI : (0.9391, 0.9895)
    No Information Rate : 0.6635          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.9372          
                                          
 Mcnemar's Test P-Value : 0.2207          
                                          
            Sensitivity : 0.9643          
            Specificity : 0.9859          
         Pos Pred Value : 0.9926          
         Neg Pred Value : 0.9333          
              Precision : 0.9926          
                 Recall : 0.9643          
                     F1 : 0.9783          
             Prevalence : 0.6635          
         Detection Rate : 0.6398          
   Detection Prevalence : 0.6445          
      Balanced Accuracy : 0.9751          
                                          
       'Positive' Class : 2               
                                          

The Kappa result shows this is an excellent model with high accuracy and balanced accuracy. The CCA test will be considered next.

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

#Fit model with training data and review details
model_svm1 <- svm(x1, y1, kernel='radial')
print(model_svm1)

Call:
svm.default(x = x1, y = y1, kernel = "radial")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 

Number of Support Vectors:  72
summary(model_svm1)

Call:
svm.default(x = x1, y = y1, kernel = "radial")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 

Number of Support Vectors:  72

 ( 21 51 )


Number of Classes:  2 

Levels: 
 2 4
#test with test data
x1t <- test_cca[,-9]
y1t <-as.factor(test_cca$Diagnosis)
pred_y1t <- predict(model_svm1, x1t)

#check accuracy
table(pred_y1t, y1t)
        y1t
pred_y1t   2   4
       2 141   2
       4   7  55
confusionMatrix(pred_y1t, y1t, mode="everything")
Confusion Matrix and Statistics

          Reference
Prediction   2   4
         2 141   2
         4   7  55
                                          
               Accuracy : 0.9561          
                 95% CI : (0.9183, 0.9797)
    No Information Rate : 0.722           
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.8935          
                                          
 Mcnemar's Test P-Value : 0.1824          
                                          
            Sensitivity : 0.9527          
            Specificity : 0.9649          
         Pos Pred Value : 0.9860          
         Neg Pred Value : 0.8871          
              Precision : 0.9860          
                 Recall : 0.9527          
                     F1 : 0.9691          
             Prevalence : 0.7220          
         Detection Rate : 0.6878          
   Detection Prevalence : 0.6976          
      Balanced Accuracy : 0.9588          
                                          
       'Positive' Class : 2               
                                          

Which Model Produced the Best Results?

The following code block captures the model which produced the best results, based on Kappa and core metric (Sensitivity) as this is a minority classification problem.

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

Complete case analysis produced an excellent model also, with a lower Kappa rate and metrics than the baseline MI model. Accuracy, balanced accuracy and the F1 score are all slightly lower. Although the MCAR test result indicated that data is MCAR, the missing data pattern in one variable only suggested MAR or MNAR. The better performance of the MI model over CCA, despite a low missingness rate, suggests that MAR or MNAR is more likely or that valuable information is included in the deleted data. To test MNAR further, the SVM model will be tested against the highest delta adjusted imputation to see if there is a big impact on results. If CCA produces a better result, this may be due to data bias.

Does Highest Delta Adjustment Have Big Impact on Results?

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

#Fit model with training data and review details
model_svm5 <- svm(x5, y5, kernel='radial')
print(model_svm5)

Call:
svm.default(x = x5, y = y5, kernel = "radial")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 

Number of Support Vectors:  86
summary(model_svm5)

Call:
svm.default(x = x5, y = y5, kernel = "radial")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 

Number of Support Vectors:  86

 ( 29 57 )


Number of Classes:  2 

Levels: 
 2 4
#test with test data
x5t <- test_d5[,-9]
y5t <-as.factor(test_d5$Diagnosis)
pred_y5t <- predict(model_svm5, x5t)

#check accuracy
table(pred_y5t, y5t)
        y5t
pred_y5t   2   4
       2 135   1
       4   5  70
confusionMatrix(pred_y5t, y5t, mode="everything")
Confusion Matrix and Statistics

          Reference
Prediction   2   4
         2 135   1
         4   5  70
                                          
               Accuracy : 0.9716          
                 95% CI : (0.9391, 0.9895)
    No Information Rate : 0.6635          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.9372          
                                          
 Mcnemar's Test P-Value : 0.2207          
                                          
            Sensitivity : 0.9643          
            Specificity : 0.9859          
         Pos Pred Value : 0.9926          
         Neg Pred Value : 0.9333          
              Precision : 0.9926          
                 Recall : 0.9643          
                     F1 : 0.9783          
             Prevalence : 0.6635          
         Detection Rate : 0.6398          
   Detection Prevalence : 0.6445          
      Balanced Accuracy : 0.9751          
                                          
       'Positive' Class : 2               
                                          

There is not a big impact in this example, suggesting results are relatively stable, that multiple imputation produces the best results and that a pattern mixture model is not necessary. The impact result is captured as a Yes or No response in the code below.

#Is there a big impact on result?
delta_impact = 'No'

Optional Step: Misclassified Analysis

Look at which rows were misclassified by the MI and CCA models.

test_cca <-as.data.frame(test_cca)
misclass_mi <- which(pred_yt != test_mi[,9])
misclass_cca <- which(pred_y1t != test_cca[,9])

Print misclassified row from MI and CCA Model results.

print(misclass_mi)
[1]   1   2  61  70  99 197
print(misclass_cca)
[1]   1   2  15  43  58  67  79  96 135

Create objects with misclassified rows and print results.

#the model misclassified the following rows
#subset data
misclass_rows_mi <- test_mi[c(1,2,61,70,99,197),]
misclass_rows_cca <- test_cca[c(1,2,15,43,58,67,79,96,135),]

print(misclass_rows_mi)
print(misclass_rows_cca)

Whilst 5 of the 6 misclassified results from the multiple imputation model also appear in the model using CCA, there is still room for improvement. However, several of the misclassified results have data that are more representative of the other diagnosis - such as higher measures for CellShape and BareNuclei.

Final Visualisation of Results

library(ROCR) # for ROC curve
#calculations for ROC curve
predictions <- as.numeric(predict(model_svm, test_mi[,-9], type="response"))
pred <- prediction(predictions, test_mi$Diagnosis)
perf <-performance(pred, measure="tpr", x.measure="fpr")
plot(perf, col="dodgerblue1", main="ROC Curve Shows Good Separation of Data")

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.02315485 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? No"

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] "Hypothesis unproven. MCAR Test and missing data pattern indicate different missing data mechanisms."

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. This should be a reliable approach for this missing data problem."

References

–Last updated: August 2023 –

–End–

LS0tDQp0aXRsZTogIlZhYXIgUiBOb3RlYm9vazogQnJlYXN0IENhbmNlciBXaXNjb25zaW4gT3JpZ2luYWwiDQphdXRob3I6ICJBbWFuZGEgSGFycmlzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQpwYXJhbXM6DQogIHByaW50Y29kZTogdHJ1ZQ0KLS0tDQoNClRoaXMgaXMgYW4gZXhlbXBsYXIgd29ya2Jvb2sgc2hvd2luZyBob3cgdGhlIFZhYXIgTm90ZWJvb2sgY2FuIGJlIGFwcGxpZWQgdG8gVUNJJ3MgQnJlYXN0IENhbmNlciBXaXNjb25zaW4gT3JpZ2luYWwgRGF0YSAoV29sYmVyZywgMTk5MikgZGF0YXNldCB0byByZXR1cm46ICANCi0gV2hldGhlciBkYXRhIGlzIG1vcmUgbGlrZWx5IHRvIGJlIE1DQVIgb3IgTUFSL01OQVIgIA0KLSBBIHJlY29tbWVuZGVkIGFwcHJvYWNoIGZvciBtYW5hZ2luZyBtaXNzaW5nIGRhdGEgdGhhdCBjb25zaWRlcnMgc3RhYmlsaXR5IG9mIG1vZGVsIHJlc3VsdHMgIA0KLSBFdmFsdWF0aW9uIG9mIGltcHV0YXRpb24gZWZmb3J0cyBiYXNlZCBvbiBkYXRhIG1vZGVsIGV4cGVyaW1lbnQgcmVzdWx0cyAgDQoNCg0KIyBTdGVwIDE6IFJlYWQgaW4gZGF0YSBhbmQgc2hvdyBkYXRhIHN1bW1hcnkgIA0KDQpEYXRhLnRhYmxlIHBhY2thZ2UgdXNlZCB0byByZWFkIGluIGZpbGUgZnJvbSB3ZWJzaXRlLiBUaGlzIGlzIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW0gdG8gZGlhZ25vc2UgYnJlYXN0IGNhbmNlciBtYWxpZ25hbmN5LCB3aGVyZSB0aGUgdGFyZ2V0IGRpYWdub3NpcyBpcyB0aGUgbWlub3JpdHkgY2xhc3MuIA0KDQpgYGB7cn0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCmRmIDwtIGZyZWFkKCdodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvYnJlYXN0LWNhbmNlci13aXNjb25zaW4vYnJlYXN0LWNhbmNlci13aXNjb25zaW4uZGF0YScsIGhlYWRlcj1GQUxTRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KaGVhZChkZikNCnN1bW1hcnkoZGYpDQoNCmxpYnJhcnkodGlkeXZlcnNlKSAjIHRvIHVzZSBwaXBlcw0KI2NvdW50IG9icyBwZXIgY2xhc3MNCmRmICU+JQ0KICBjb3VudChWMTEpDQpgYGANCg0KDQpOb3RlIGluIHN1bW1hcnkgb2YgZGF0YWZyYW1lIHRoYXQgVjcgaXMgdGhlIG9ubHkgY2hhcmFjdGVyIHZhcmlhYmxlLCB0aGUgb3RoZXJzIGFyZSBpbnRlZ2Vycy4gVGhlcmUgYXJlIDI0MSBtYWxpZ25hbnQgYW5kIDQ1OCBiZW5pZ24gb2JzZXJ2YXRpb25zIGluIHRoZSBkYXRhLg0KDQojIFN0ZXAgMjogQ29weSBhbmQgVGlkeSBEYXRhDQpUaWR5dmVyc2UgcGFja2FnZSB1c2VkIHRvIHJlbmFtZSB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KZGZjcCA8LSBkZiAjY29weSBkYXRhDQoNCiNyZW5hbWUgdmFyaWFibGVzIHRvIHNvbWV0aGluZyBtb3JlIG1lYW5pbmdmdWwNCmRmY3AgPC0gcmVuYW1lIChkZmNwLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZUlEPVYxLCBDbHVtcFRoaWNrbmVzcz1WMiwgQ2VsbFNpemU9VjMsIENlbGxTaGFwZT1WNCwgQWRoZXNpb249VjUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgU2luZ2xlRVNpemU9VjYsIEJhcmVOdWNsZWk9VjcsIEJsYW5kQ2hyb21hdGluPVY4LCBOb3JtYWxOdWNsZW9saT1WOSwgDQogICAgICAgICAgICAgICAgICAgICAgICBNaXRvc2VzPVYxMCwgRGlhZ25vc2lzPVYxMSkNCg0KI0NvbnZlcnQgVjcgdmFyaWFibGUgdG8gaW50ZWdlciBhbHNvDQojVGhpcyBpcyBub3cgbmFtZWQgQmFyZU51Y2xlaQ0KZGZjcCRCYXJlTnVjbGVpIDwtIGFzLmludGVnZXIoZGZjcCRCYXJlTnVjbGVpKQ0KDQpgYGANCg0KDQojIyBDaGVjayBmb3IgRHVwbGljYXRlcw0KRHVwbGljYXRlcyBjaGVja2VkIHVzaW5nIG5fZGlzdGluY3QgaW4gdGlkeXZlcnNlIHBhY2thZ2UNCg0KYGBge3J9DQpuX2Rpc3RpbmN0KGRmY3ApDQoNCiNmaWx0ZXIgdG8gY2hlY2sgYW5kIHJldmlldyBhbnkgZHVwbGljYXRlIHJlY29yZHMNCmR1cGxpY2F0ZXMgPC0gZGZjcCAlPiUNCiAgZmlsdGVyKGR1cGxpY2F0ZWQoLikpDQoNCnByaW50KGR1cGxpY2F0ZXMpDQpgYGANClRoZXJlIGFyZSA2OTEgdW5pcXVlIHJlY29yZHMgb3V0IG9mIDY5OSBvYnNlcnZhdGlvbnMoOCBkdXBsaWNhdGVzIHByaW50ZWQpLiANCg0KIyMjIFJlbW92ZSBkdXBsaWNhdGVzIGFuZCBJRCB2YXJpYWJsZQ0KDQpgYGB7cn0NCmRmY3AgPC0gZGZjcCAlPiUgZGlzdGluY3QoKSAjNjkxIG9icyByZW1haW5pbmcNCmRmY3AgPC0gc2VsZWN0KGRmY3AsLWMoc2FtcGxlSUQpKSAjcmVtb3ZlIHNhbXBsZUlEIGNvbHVtbiAxMCB2YXJpYWJsZXMgcmVtYWluDQpgYGANCg0KIyBTdGVwIDM6IFZpc3VhbGlzZSBNaXNzaW5nIERhdGEgYW5kIHJ1biBNQ0FSIFRlc3QNCg0KUGFja2FnZXMgdXNlZDogdmlzZGF0IHRvIGV4cGxvcmUgbWlzc2luZyB2YWx1ZXMsIGdncGxvdCB0byBleHBsb3JlIG1pc3NpbmcgZGF0YSBmdXJ0aGVyIGFuZCBuYW5pYXIgZm9yIGdlb21fbWlzc19wb2ludCBmdW5jdGlvbiBhbmQgTUNBUiB0ZXN0LiAgDQoqKlRoZXJlIHdpbGwgYmUgYSB3YXJuaW5nIHdpdGggTUNBUiB0ZXN0IGlmIG5vbi1udW1lcmljIGNvbHVtbnMgYXJlIHByZXNlbnQuIElmIHAtdmFsdWUgPjAuMDUgbGlrZWx5IHRvIGJlIE1DQVIgYXMgbm90IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuKioNCg0KYGBge3J9DQpsaWJyYXJ5KHZpc2RhdCkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkobmFuaWFyKQ0KDQp2aXNfbWlzcyhkZmNwKQ0KbWlzc192YXJfc3VtbWFyeShkZmNwKQ0KDQpnZ3Bsb3QoZGZjcCwNCiAgICAgICBhZXMgKHggPSBEaWFnbm9zaXMsDQogICAgICAgICAgICB5ID0gQmFyZU51Y2xlaSwpKSArDQogIGdlb21fbWlzc19wb2ludCgpICsNCiAgZ2d0aXRsZSAoIkdyYXBoIFNob3dpbmcgTWlzc2luZyBEYXRhIFBhdHRlcm4gQnkgQ2xhc3MgRGlhZ25vc2lzIikNCmdncGxvdA0KDQptY2FyX3Rlc3QoZGZjcCkNCmBgYA0KIyBTdGVwIDQ6IExvb2sgYXQgT3V0cHV0cyBmcm9tIFZpc3VhbGlzaW5nIHRoZSBNaXNzaW5nIERhdGEgYW5kIEFuc3dlciB0aGUgRm9sbG93aW5nIFF1ZXN0aW9ucw0KDQojIyMjIFdoYXQgaXMgdGhlIHAudmFsdWUgcmV0dXJuZWQgYnkgdGhlIE1DQVIgVGVzdD8NCklmIHRoZSBzdGF0aXN0aWMgaXMgaGlnaCBhbmQgdGhlIHAudmFsdWUgPDAuMDUgaXQgaXMgbGlrZWx5IHRvIGJlIE1OQVIgb3IgTUFSIGFuZCBjYW5ub3QgYmUgaWdub3JlZC4gQSBwLXZhbHVlID4wLjA1IGluZGljYXRlcyBNQ0FSIGJ1dCBvdGhlciBpbmZvcm1hdGlvbiB3aWxsIGJlIGNvbnNpZGVyZWQgYWxzby4gVGhlIHZhbHVlIGlzIHNldCBhcyBhIHZhcmlhYmxlIGluIHRoZSBmb2xsb3dpbmcgY29kZS4NCmBgYHtyfQ0KI3NldCBNQ0FSIFRlc3QgcC52YWx1ZSBhcyB2YXJpYWJsZQ0KbWNhcl9wcmVzdWx0ID0gMC4yMTY4Njg2DQoNCmBgYA0KDQoNCiMjIyMgSXMgZGF0YSBtb3JlIGxpa2VseSB0byBiZSBtaXNzaW5nIGluIHNvbWUgdmFyaWFibGVzPw0KSWYgc28sIHRoaXMgaW5kaWNhdGVzIHRoYXQgdGhlIGRhdGEgY291bGQgYmUgbWlzc2luZyBhdCByYW5kb20gKE1BUikgb3IgbWlzc2luZyBub3QgYXQgcmFuZG9tIChNTkFSKSBhbmQgY2FuIHRoZXJlZm9yZSBub3QgYmUgaWdub3JlZC4gWWVzIG9yIE5vIGlzIHNldCBhcyBhIHZhcmlhYmxlIGluIHRoZSBmb2xsb3dpbmcgY29kZS4NCmBgYHtyfQ0KI3NldCBsaWtlbGlob29kIHZhcmlhYmxlDQpsaWtlbGlob29kID0gIlllcyINCmBgYA0KDQojIyMjIElzIGRhdGEgbWlzc2luZyBmcm9tIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgb25seT8NCklmIHNvLCB0aGlzIHN1Z2dlc3RzIHRoYXQgY29tcGxldGUgY2FzZSBhbmFseXNpcyBtYXkgYmUgdGhlIGJlc3Qgb3B0aW9uLiBIb3dldmVyLCBvdGhlciBmYWN0b3JzIG5lZWQgdG8gYmUgY29uc2lkZXJlZCBhbHNvLiBZZXMgb3IgTm8gaXMgc2V0IGFzIHZhcmlhYmxlIGluIHRoZSBmb2xsb3dpbmcgY29kZS4NCmBgYHtyfQ0KI3NldCBkZXBlbmRlbnQgbWlzc2luZ25lc3MgdmFyaWFibGUNCmRlcGVuZGVudF9vbmx5ID0gIk5vIg0KYGBgDQoNCg0KIyMjIyBXaGF0IHBlcmNlbnRhZ2Ugb2YgZGF0YSBpcyBtaXNzaW5nPw0KSWYgaXQncyBiZXR3ZWVuIDIwLTUwJSBtdWx0aXBsZSBpbXB1dGF0aW9uIGlzIGxpa2VseSB0byByZXN1bHQgaW4gYSBtb3JlIGVmZmVjdGl2ZSwgdW5iaWFzZWQgbW9kZWwuIFRoZSB2YWx1ZSBpcyBzZXQgYXMgYSB2YXJpYWJsZSBpbiB0aGUgZm9sbG93aW5nIGNvZGUuDQpgYGB7cn0NCiNzZXQgbWlzc2luZ25lc3MgdmFyaWFibGUgdmFsdWUNCm1pc3NpbmduZXNzID0gMC4wMjMxNTQ4NQ0KYGBgDQoNCg0KSXQncyBnb29kIHByYWN0aWNlIHRvIHRlc3QgZGlmZmVyZW50IGFwcHJvYWNoZXMgYW5kIHRoZSBWYWFyIE5vdGVib29rcyB3aWxsIGNvbnNpZGVyIHRoZXNlLCBhcyB3ZWxsIGFzIHRoZSB2YXJpYWJsZSB2YWx1ZXMganVzdCBzZXQgaW4gcmVjb21tZW5kYXRpb25zLg0KDQoNCiMjIFZpc3VhbGlzZSBDb3JyZWxhdGlvbg0KVmlzdWFsaXNlIGNvcnJlbGF0aW9uIGZvciBudW1lcmljYWwgdmFyaWFibGVzLg0KDQpgYGB7cn0NCmRmY3BfeCA8LSBzZWxlY3RfaWYoZGZjcCwgaXMubnVtZXJpYykNCmNvcihkZmNwX3gpDQoNCnZpc19jb3IoZGZjcF94KQ0KYGBgDQoNClRoZXJlIGlzIGEgaGlnaCBsZXZlbCBvZiBjb3JyZWxhdGlvbiBiZXR3ZWVuIHZhcmlhYmxlcyBhbmQgbGlrZWx5IGNvLWNvcnJlbGF0aW9uIGJldHdlZW4gQ2VsbFNpemUgYW5kIENlbGxTaGFwZSBmb3IgdGhpcyBkYXRhc2V0LiANCg0KYGBge3J9DQpsaWJyYXJ5KEdHYWxseSkgI2dncGxvdDIgZXh0ZW5zaW9uIGZvciBwYWlycyBtYXRyaXgNCiNDaGFuZ2UgRGlhZ25vc2lzIGZyb20gaW50ZWdlciB0byBmYWN0b3INCmRmY3AkRGlhZ25vc2lzIDwtIGFzLmZhY3RvcihkZmNwJERpYWdub3NpcykNCg0KcG0gPC0gZ2dwYWlycyhkZmNwLCBjb2x1bW5zID0gMToxMCwgZ2dwbG90Mjo6YWVzKGNvbG91ciA9IERpYWdub3NpcyksIGxvd2VyPWxpc3QoY29tYm89d3JhcCgiZmFjZXRoaXN0IiwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaW53aWR0aD0wLjUpKSkNCnBtDQoNCmBgYA0KDQpNYWpvcml0eSBjbGFzcyAtIGJlbmlnbiAtIGhhcyBhIGxvdCBtb3JlIG91dGxpZXJzIGFuZCB0aGVyZSBpcyBhIGhpZ2ggcG9zaXRpdmUgc2tldyB0byB0aGUgYmVuaWduIGNsYXNzIGFsc28gaW4gdGhlIGRhdGEuIFRoZXJlIGlzIGEgbW9kZXJhdGUgbmVnYXRpdmUgc2tldyBvbiBzb21lIG9mIHRoZSBtYWxpZ25hbnQgY2xhc3MgdmFyaWFibGVzLg0KDQojIyBDYWxjdWxhdGUgU2tldw0KDQpgYGB7cn0NCmxpYnJhcnkocHN5Y2gpICNmb3Igc2tld25lc3MgZnVuY3Rpb24NCnNrZXcoZGZjcF94KQ0KYGBgDQoNCiMgU3RlcCA1OiB3aGljaCB2YXJpYWJsZXMgYW5kIHZhbHVlcyBhcmUgaW1wb3J0YW50IGZvciBwcmVkaWN0aW5nIHByb3BvcnRpb24gb2YgbWlzc2luZ25lc3M/DQoNCnJwYXJ0IGFuZCBycGFydC5wbG90IHBhY2thZ2VzIGFyZSB1c2VkIHRvIHBsb3QgYSBzaW1wbGUgY2xhc3NpZmljYXRpb24gdHJlZS4NCg0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KDQpkZmNwICU+JQ0KICBhZGRfcHJvcF9taXNzKCkgJT4lDQogIHJwYXJ0KHByb3BfbWlzc19hbGwgfiAuLCBkYXRhPS4pICU+JQ0KICBwcnAodHlwZT00LCBleHRyYSA9IDEwMSwgcm91bmRpbnQ9RkFMU0UsIHByZWZpeD0iUHJvcC5NaXNzID0gIikNCmBgYA0KDQojIyMjIFdoaWNoIHZhcmlhYmxlIGlzIGF0IHRoZSB0b3Agb2YgdGhlIHRyZWU/DQpBZGhlc2lvbiBpcyByZXR1cm5lZCBmb3IgdGhpcyBkYXRhc2V0LiBWYXJpYWJsZSB2YWx1ZSBmb3IgbWlzc2luZ25lc3MgaW5mbHVlbmNlciBzZXQgaW4gY29kZSBiZWxvdy4NCmBgYHtyfQ0KI3NldCB2YWx1ZSBmb3IgbWlzc2luZ25lc3MgaW5mbHVlbmNlcg0KbWlzc19pbmZsdWVuY2VyPSJBZGhlc2lvbiINCmBgYA0KDQpgYGB7cn0NCmZ4cHQgPC0gZmx1eHBsb3QoZGZjcCkNCiMgVmFyaWFibGVzIHdpdGggaGlnaGVyIG91dGZsdXggbWF5IGJlIG1vcmUgcG93ZXJmdWwgcHJlZGljdG9ycy4gTW9yZSBtZWFuaW5nZnVsIHdoZXJlIGRhdGEgaXMgbWlzc2luZyBmcm9tIG1vcmUgdGhhbiBvbmUgdmFyaWFibGUuDQpgYGANCiMgU3RlcCA2OiBDcmVhdGUgQ29tcGxldGUgQ2FzZSBhbmQgTXVsdGlwbGUgSW1wdXRlZCBEYXRhc2V0cw0KQ3JlYXRlIGNvbXBsZXRlIGRhdGFzZXQgYnkgZGVsZXRpbmcgcmVjb3JkcyB3aXRoIG1pc3NpbmcgdmFsdWVzIGFuZCBydW4gTXVsdGlwbGUgSW1wdXRhdGlvbiB1c2luZyBNSUNFLg0KDQpgYGB7cn0NCmRmY3BfY2NhIDwtIGRmY3AgJT4lDQogIGZpbHRlcighaXMubmEoQmFyZU51Y2xlaSkpDQoNCmxpYnJhcnkobWljZSkNCmluaXQgPSBtaWNlKGRmY3AsIG1heGl0PTApIA0KbWV0aCA9IGluaXQkbWV0aG9kDQpwcmVkTSA9IGluaXQkcHJlZGljdG9yTWF0cml4DQoNCiNjcmVhdGUgaW1wdXRlZCBkYXRhDQpzZXQuc2VlZCgxMjMpDQpkZmNwX2ltcCA9IG1pY2UoZGZjcCwgbWV0aG9kPW1ldGgsIHByZWRpY3Rvck1hdHJpeD1wcmVkTSwgbT01KQ0KI2NyZWF0ZSBkYXRhc2V0IGFmdGVyIGltcHV0YXRpb24NCmRmY3BfbWkgPC0gY29tcGxldGUoZGZjcF9pbXApDQoNCmBgYA0KIyMjIENoZWNrIEltcHV0YXRpb24gVmlzdWFsbHkNCkFzIHdhcm5pbmdzIGdpdmVuLCBjaGVjayBpbXB1dGF0aW9uIHdhcyBwb3NzaWJsZS4NCmBgYHtyfQ0KI2NoZWNrIGZvciBtaXNzaW5nIGRhdGEgaW4gTUkgYW5kIENDQSBkYXRhc2V0DQp2aXNfbWlzcyhkZmNwX21pKQ0KdmlzX21pc3MoZGZjcF9jY2EpDQpgYGANCg0KDQoNCiMgU3RlcCA3OiBDb25kdWN0IE1OQVIgU2Vuc2l0aXZpdHkgVGVzdA0KDQpVc2UgdGhlIHZhcmlhYmxlIHdoaWNoIGhhcyB0aGUgbW9zdCBpbmZsdWVuY2Ugb24gcHJvcG9ydGlvbiBvZiBtaXNzaW5nbmVzcywgYWNjb3JkaW5nIHRvIHN0ZXAgNSBhbmQgbG9vayBhdCB0aGUgcmFuZ2Ugb2YgdmFsdWVzIGluIHRoaXMgdmFyaWFibGUuIEdlbmVyYXRlIGltcHV0YXRpb25zIHVuZGVyIGRlbHRhIGFkanVzdG1lbnQgdG8gaW1pdGF0ZSBkZXZpYXRpb25zIGZyb20gTUFSIChHaW5rIGFuZCBWYW4gQnV1cmVuLCBubyBkYXRlKS4gVGhlIGNvZGUgdXNlcyAwIGZvciBNQVIgYW5kIGEgcmVhc29uYWJsZSByYW5nZSBiYXNlZCBvbiB0aGUgdmFyaWFibGUgcmFuZ2UgYXMgdGhlIGRlbHRhIHZhbHVlcyB0byB0ZXN0IE1OQVIuDQoNCmBgYHtyfQ0KDQojR2VuZXJhdGUgaW1wdXRhdGlvbnMgdW5kZXIgZGVsdGEgYWRqdXN0bWVudA0KZGVsdGEgPC0gYygwLCArMSwgKzIsICszLCArNCkNCmltcC5kIDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aChkZWx0YSkpDQpwb3N0IDwtZGZjcF9pbXAkcG9zdA0KDQpmb3IgKGkgaW4gMTpsZW5ndGgoZGVsdGEpKSB7DQogIGQgPC0gZGVsdGFbaV0NCiAgY21kIDwtIHBhc3RlKCJpbXBbW2pdXVssaV0gPC0gaW1wW1tqXV1bLGldICsiLCBkKQ0KICBwb3N0WyJCYXJlTnVjbGVpIl0gPC0gY21kDQogIGltcCA8LSBtaWNlKGRmY3AsIHBvc3QgPSBwb3N0LCBtYXhpdCA9IDUsDQogICAgICAgICAgICAgIHNlZWQgPSBpLCBwcmludD1GQUxTRSkNCiAgaW1wLmRbW2ldXSA8LSBpbXANCn0NCmBgYA0KDQojIyBJbnNwZWN0IEltcHV0YXRpb25zDQoNClRoZSBmaXJzdCBwbG90IGlzIGJhc2VkIG9uIG5vIGRlbHRhIGFkanVzdG1lbnQgYW5kIHRoZSBzZWNvbmQgcGxvdCBpcyB0aGUgaGlnaGVzdCBhZGp1c3RtZW50LiBUaGUgWSBheGlzIHNjYWxlIGlzIHNldCB0byB0aGUgc2FtZSB2YWx1ZSwgc28gdGhhdCBkaWZmZXJlbmNlcyBjYW4gYmUgdmlzdWFsaXNlZCBtb3JlIGVhc2lseS4gDQoNCmBgYHtyfQ0KZGVuc2l0eXBsb3QoaW1wLmRbWzFdXSxsd2Q9MywgeWxpbT1jKDAsNCkpDQpkZW5zaXR5cGxvdChpbXAuZFtbNV1dLGx3ZD0zLCB5bGltPWMoMCw0KSkNCmBgYA0KDQpGaW5kIG91dCBtb3JlIGFib3V0IHNlbnNpdGl2aXR5IGFuYWx5c2lzIGluIHRoZSBjb250ZXh0IG9mIG1pc3NpbmcgZGF0YSBmcm9tIEdlcmtvIFZpbmsgYW5kIFN0ZWYgdmFuIEJ1dXJlbidzIHZpZ25ldHRlIFttaWNlOiBBbiBhcHByb2FjaCB0byBzZW5zaXRpdml0eSBhbmFseXNpc10oaHR0cHM6Ly93d3cuZ2Vya292aW5rLmNvbS9taWNlVmlnbmV0dGVzL1NlbnNpdGl2aXR5X2FuYWx5c2lzL1NlbnNpdGl2aXR5X2FuYWx5c2lzLmh0bWwpIChWaW5rIGFuZCB2YW4gQnV1cmVuLCBubyBkYXRlKS4gDQoNClRoZSBzZWNvbmQgZGVuc2l0eSBwbG90IGNvbnNpZGVycyBpbXB1dGF0aW9ucyB1bmRlciB0aGUgbGFyZ2VzdCBhZGp1c3RtZW50IGFuZCBpdCBoYXMgbm90IHJlYWxseSBoYWQgYW4gZWZmZWN0IG9uIHRoZSBpbXB1dGF0aW9ucy4gVGhlIGJsdWUgbGluZSBpcyBmYWlybHkgY29uc2lzdGVudCBiZXR3ZWVuIHRoZSB0d28gcGxvdHMuIA0KDQojIyBDcmVhdGUgQ29tcGxldGUgRGF0YXNldHMgd2l0aCBEZWx0YSBJbXB1dGF0aW9ucyAxIHRvIDUNCkNyZWF0ZSBjb21wbGV0ZSBkYXRhc2V0cyBhbmQgY2hlY2sgaW1wdXRhdGlvbnMgaGF2ZSB3b3JrZWQgYnkgdmlzdWFsaXNpbmcgbWlzc2luZyBkYXRhLg0KYGBge3J9DQojcmV2aWV3IGltcHV0YXRpb25zDQpwcmludChpbXAuZFtbMl1dJGltcCRCYXJlTnVjbGVpKQ0KcHJpbnQoaW1wLmRbWzNdXSRpbXAkQmFyZU51Y2xlaSkNCnByaW50KGltcC5kW1s0XV0kaW1wJEJhcmVOdWNsZWkpDQpwcmludChpbXAuZFtbNV1dJGltcCRCYXJlTnVjbGVpKQ0KYGBgDQoNCg0KYGBge3J9DQojZGVsdGEgMSBpcyB0aGUgZGZjcF9taSBkYXRhDQpkZmNwX21pX2QyIDwtIGNvbXBsZXRlKGltcC5kW1syXV0pDQpkZmNwX21pX2QzIDwtIGNvbXBsZXRlKGltcC5kW1szXV0pDQpkZmNwX21pX2Q0IDwtIGNvbXBsZXRlKGltcC5kW1s0XV0pDQpkZmNwX21pX2Q1IDwtIGNvbXBsZXRlKGltcC5kW1s1XV0pDQoNCg0KdmlzX21pc3MoZGZjcF9taV9kMikNCnZpc19taXNzKGRmY3BfbWlfZDMpDQp2aXNfbWlzcyhkZmNwX21pX2Q0KQ0KdmlzX21pc3MoZGZjcF9taV9kNSkNCg0KYGBgDQoNCiMjIE9wdGlvbmFsIFN0ZXA6IFJlbW92ZSBPdXRsaWVycw0KVGhlcmUgY2FuIGJlIHZhbHVhYmxlIGluZm9ybWF0aW9uIGluIG91dGxpZXJzIGFuZCBpdCBpcyBnb29kIHByYWN0aWNlIHRvIHJlbW92ZSBvYnZpb3VzIGVycm9ycyBvbmx5IC0gc3VjaCBhcyBpbXBvc3NpYmxlIHZhbHVlcyBmb3IgcGFydGljdWxhciB2YXJpYWJsZXMuIFRoZSBsYXJnZSBudW1iZXIgb2Ygb3V0bGllcnMgb2JzZXJ2ZWQgaW4gdGhlIGJlbmlnbiBjbGFzcyBpbiB0aGUgcGFpcnMgY2hhcnQgY291bGQgYmUgYW4gYWNjdXJhdGUgcmVwcmVzZW50YXRpb24gb2YgdGhlIG5hdHVyYWwgdmFyaWFiaWxpdHkgaW4gZGF0YSBhbmQgZGlhZ25vc3RpYyBjaGFsbGVuZ2UuDQoNCg0KIyBTdGVwIDg6IFNlbGVjdCBGZWF0dXJlcw0KVGhpcyBjb2RlIGlzIGFuIGV4YW1wbGUgb2YgaG93IHRvIGRldGVybWluZSB3aGljaCB2YXJpYWJsZXMgaGF2ZSBhYm92ZSAwLjkgY29ycmVsYXRpb24sIHVzaW5nIHRoZSBjYXJldCBwYWNrYWdlLCBhcyBoaWdobHktY29ycmVsYXRlZCBmZWF0dXJlcyBkbyBub3QgZ2VuZXJhbGx5IGltcHJvdmUgbW9kZWxzLiBUaGVuLCByZW1vdmUgYW55IHZhcmlhYmxlcyBpZGVudGlmaWVkIGZyb20gYWxsIGRhdGFzZXRzLg0KDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQpkZmNwX21pX2NvciA8LSBjb3IoZGZjcF9taSU+JSBzZWxlY3QoLURpYWdub3NpcykpDQpkZmNwX21pIDwtIGRmY3BfbWkgJT4lIHNlbGVjdCgtZmluZENvcnJlbGF0aW9uKGRmY3BfbWlfY29yLCBjdXRvZmYgPSAwLjkpKQ0KDQojc2hvdyBjb2x1bW4gbmFtZSBkaWZmZXJlbmNlIGJldHdlZW4gb3JpZ2luYWwgZGF0YSBhbmQgc2VsZWN0ZWQgZmVhdHVyZXMNCnNldGRpZmYobmFtZXMoZGZjcCksIG5hbWVzKGRmY3BfbWkpKQ0KDQpgYGANCg0KQ2VsbFNpemUgaGFzIGJlZW4gcmVtb3ZlZC4NCg0KYGBge3J9DQojcmVtb3ZlIENlbGxTaXplIGZyb20gb3RoZXIgZGF0YXNldHMgYWxzbw0KDQpkZmNwX2NjYSA8LSBkZmNwX2NjYSAlPiUgc2VsZWN0KC1DZWxsU2l6ZSkNCmRmY3BfbWlfZDIgPC0gZGZjcF9taV9kMiAlPiUgc2VsZWN0KC1DZWxsU2l6ZSkNCmRmY3BfbWlfZDMgPC0gZGZjcF9taV9kMyAlPiUgc2VsZWN0KC1DZWxsU2l6ZSkNCmRmY3BfbWlfZDQgPC0gZGZjcF9taV9kNCAlPiUgc2VsZWN0KC1DZWxsU2l6ZSkNCmRmY3BfbWlfZDUgPC0gZGZjcF9taV9kNSAlPiUgc2VsZWN0KC1DZWxsU2l6ZSkNCmBgYA0KDQojIyBPcHRpb25hbCBzdGVwOiBUcmFuc2Zvcm0gZGF0YQ0KU2NhbGluZyBkYXRhIGFsbG93cyBtb2RlbHMgdG8gY29tcGFyZSByZWxhdGl2ZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gZGF0YSBwb2ludHMgbW9yZSBlZmZlY3RpdmVseS4gVGhlIEJyZWFzdCBDYW5jZXIgV2lzY29uc2luIChPcmlnaW5hbCkgRGF0YSBTZXQgaXMgYWxyZWFkeSBzY2FsZWQgKDEgLTEwKS4NCg0KDQojIFN0ZXAgOTogQnVpbGQgYW5kIFRlc3QgTW9kZWxzDQpUaGlzIG5vdGVib29rIHVzZXMgYSBTdXBwb3J0IFZlY3RvciBNYWNoaW5lIChTVk0pIE1vZGVsLiBPdGhlciBtb2RlbHMgYXJlIGF2YWlsYWJsZS4NCg0KIyMgQ3JlYXRlIFRyYWluaW5nIGFuZCBUZXN0IFNldHMNClNlZWRzIHNldCBmb3IgcmVwcm9kdWNpYmlsaXR5Lg0KDQpgYGB7cn0NCiNtaSBkYXRhDQpzZXQuc2VlZCgxMjMpDQppbmRleCA8LSBzYW1wbGUoMiwgbnJvdyhkZmNwX21pKSwNCiAgICAgICAgICAgICAgcmVwbGFjZSA9IFRSVUUsDQogICAgICAgICAgICAgIHByb2IgPSBjKDAuNywgMC4zKSkNCnRyYWluX21pIDwtIGRmY3BfbWlbaW5kZXg9PTEsXQ0KdGVzdF9taSA8LSBkZmNwX21pW2luZGV4PT0yLF0gDQoNCiNjY2EgZGF0YQ0Kc2V0LnNlZWQoMTIzKQ0KaW5kZXgxIDwtIHNhbXBsZSgyLCBucm93KGRmY3BfY2NhKSwNCiAgICAgICAgICAgICAgICAgcmVwbGFjZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgIHByb2IgPSBjKDAuNywgMC4zKSkNCnRyYWluX2NjYSA8LSBkZmNwX2NjYVtpbmRleDE9PTEsXSANCnRlc3RfY2NhIDwtIGRmY3BfY2NhW2luZGV4MT09MixdIA0KDQoNCiNkZWx0YTIgZGF0YQ0Kc2V0LnNlZWQoMTIzKQ0KaW5kZXgyIDwtIHNhbXBsZSgyLCBucm93KGRmY3BfbWlfZDIpLA0KICAgICAgICAgICAgICAgICByZXBsYWNlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgcHJvYiA9IGMoMC43LCAwLjMpKQ0KdHJhaW5fZDIgPC0gZGZjcF9taV9kMltpbmRleDI9PTEsXSANCnRlc3RfZDIgPC0gZGZjcF9taV9kMltpbmRleDI9PTIsXSANCg0KI2RlbHRhMyBkYXRhDQpzZXQuc2VlZCgxMjMpDQppbmRleDMgPC0gc2FtcGxlKDIsIG5yb3coZGZjcF9taV9kMyksDQogICAgICAgICAgICAgICAgIHJlcGxhY2UgPSBUUlVFLA0KICAgICAgICAgICAgICAgICBwcm9iID0gYygwLjcsIDAuMykpDQp0cmFpbl9kMyA8LSBkZmNwX21pX2QzW2luZGV4Mz09MSxdIA0KdGVzdF9kMyA8LSBkZmNwX21pX2QzW2luZGV4Mz09MixdIA0KDQojZGVsdGE0IGRhdGENCnNldC5zZWVkKDEyMykNCmluZGV4NCA8LSBzYW1wbGUoMiwgbnJvdyhkZmNwX21pX2Q0KSwNCiAgICAgICAgICAgICAgICAgcmVwbGFjZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgIHByb2IgPSBjKDAuNywgMC4zKSkNCnRyYWluX2Q0IDwtIGRmY3BfbWlfZDRbaW5kZXg0PT0xLF0gDQp0ZXN0X2Q0IDwtIGRmY3BfbWlfZDRbaW5kZXg0PT0yLF0gDQoNCiNkZWx0YTUgZGF0YQ0Kc2V0LnNlZWQoMTIzKQ0KaW5kZXg1IDwtIHNhbXBsZSgyLCBucm93KGRmY3BfbWlfZDUpLA0KICAgICAgICAgICAgICAgICByZXBsYWNlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgcHJvYiA9IGMoMC43LCAwLjMpKQ0KdHJhaW5fZDUgPC0gZGZjcF9taV9kNVtpbmRleDU9PTEsXSANCnRlc3RfZDUgPC0gZGZjcF9taV9kNVtpbmRleDU9PTIsXSANCmBgYA0KDQoNCiMjIFRlc3QgU1ZNIEtlcm5lbCBBcHByb2FjaGVzIHdpdGggVHJhaW5pbmcgRGF0YQ0KVGhpcyBjb2RlIGlzIGJhc2VkIG9uIHRoZSB2aWduZXR0ZSBmcm9tIChTYWxsYW4sIDIwMjApIFtDb21wYXJpbmcgU1ZNIGtlcm5lbHNdKGh0dHBzOi8vcnB1YnMuY29tL2ptc2FsbGFuL1NWTWtlcm5lbHMpLiANCg0KYGBge3J9DQpsaWJyYXJ5KGUxMDcxKSAjdXNpbmcgU1ZNIG1vZGVsIGZyb20gZTEwNzEgcGFja2FnZQ0KbGlicmFyeShrbml0cikgI3RvIHByb2R1Y2Uga2FibGUgdG8gdmlzdWFsaXNlIHJlc3VsdHMNCg0KYWNjdXJhY3kgPC0gc2FwcGx5KGxpc3QodHJhaW5fbWksIHRyYWluX2NjYSwgdHJhaW5fZDIsDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9kMywgdHJhaW5fZDQsIHRyYWluX2Q1KSwgDQogICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oZGYpew0KICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICBzdm1fbW9kZWxzIDwtIGxhcHBseShsaXN0KCJsaW5lYXIiLCAicG9seSIsICJyYWRpYWwiLCAic2lnbW9pZCIpLCBmdW5jdGlvbihtZXRoKXsNCiAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKHN2bShkZlssLTldLCBhcy5mYWN0b3IoZGYkRGlhZ25vc2lzKSwga2VybmVsPW1ldGgpKQ0KICAgICAgICAgICAgICAgICAgICAgfSkNCiAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgbmFtZXMoc3ZtX21vZGVscykgPC0gYygibGluZWFyIiwgInBvbHkiLCAicmFkaWFsIiwgInNpZ21vaWQiKQ0KICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICBhY2N1cmFjeSA8LSBzYXBwbHkoc3ZtX21vZGVscywgZnVuY3Rpb24oeCkgc3VtKHgkZml0dGVkID09IGRmJERpYWdub3NpcykvbnJvdyhkZikpDQogICAgICAgICAgICAgICAgICAgICBuYW1lcyhhY2N1cmFjeSkgPC0gbmFtZXMoc3ZtX21vZGVscykNCiAgICAgICAgICAgICAgICAgICAgIHJldHVybihhY2N1cmFjeSkNCiAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgIH0pDQoNCmFjY3VyYWN5IDwtIGRhdGEuZnJhbWUoYWNjdXJhY3kpDQpjb2xuYW1lcyhhY2N1cmFjeSkgPC0gYygndHJhaW5fbWknLCAndHJhaW5fY2NhJywgJ3RyYWluX2QyJywNCiAgICAgICAgICAgICAgICAgICAgICAgICd0cmFpbl9kMycsICd0cmFpbl9kNCcsICd0cmFpbl9kNScpDQoNCmFjY3VyYWN5ICU+JQ0KICBrYWJsZShkaWdpdHMgPSAzKQ0KYGBgDQoNCkFjY29yZGluZyB0byB0aGlzIHRlc3QgYWdhaW5zdCB0cmFpbmluZyBkYXRhLCBTVk0gUmFkaWFsIHByb2R1Y2VzIHRoZSBiZXN0IHJlc3VsdC4NCg0KIyMgUHJlZGljdCBBZ2FpbnN0IFRlc3QgRGF0YQ0KU3RhcnQgd2l0aCBiYXNlbGluZSBtb2RlbCB1c2luZyBtdWx0aXBsZSBpbXB1dGF0aW9uLg0KDQpgYGB7cn0NCiNDcmVhdGUgeCBhbmQgeSB2YWx1ZXMNCiNkZmNwX21pIGRhdGENCnggPC0gdHJhaW5fbWlbLC05XQ0KeSA8LSBhcy5mYWN0b3IodHJhaW5fbWkkRGlhZ25vc2lzKQ0Kc2V0LnNlZWQoMzQ1KSAjZm9yIHJlcHJvZHVjaWJpbGl0eQ0KDQojRml0IG1vZGVsIHdpdGggdHJhaW5pbmcgZGF0YSBhbmQgcmV2aWV3IGRldGFpbHMNCm1vZGVsX3N2bSA8LSBzdm0oeCwgeSwga2VybmVsPSdyYWRpYWwnKQ0KcHJpbnQobW9kZWxfc3ZtKQ0Kc3VtbWFyeShtb2RlbF9zdm0pDQoNCiN0ZXN0IHdpdGggdGVzdCBkYXRhDQp4dCA8LSB0ZXN0X21pWywtOV0NCnl0IDwtYXMuZmFjdG9yKHRlc3RfbWkkRGlhZ25vc2lzKQ0KcHJlZF95dCA8LSBwcmVkaWN0KG1vZGVsX3N2bSwgeHQpDQoNCiNjaGVjayBhY2N1cmFjeQ0KdGFibGUocHJlZF95dCwgeXQpDQpjb25mdXNpb25NYXRyaXgocHJlZF95dCwgeXQsIG1vZGU9ImV2ZXJ5dGhpbmciKQ0KYGBgDQoNClRoZSBLYXBwYSByZXN1bHQgc2hvd3MgdGhpcyBpcyBhbiBleGNlbGxlbnQgbW9kZWwgd2l0aCBoaWdoIGFjY3VyYWN5IGFuZCBiYWxhbmNlZCBhY2N1cmFjeS4gVGhlIENDQSB0ZXN0IHdpbGwgYmUgY29uc2lkZXJlZCBuZXh0Lg0KDQpgYGB7cn0NCiNDcmVhdGUgeCBhbmQgeSB2YWx1ZXMNCiNkZmNwX2NjYSBkYXRhDQp4MSA8LSB0cmFpbl9jY2FbLC05XQ0KeTEgPC0gYXMuZmFjdG9yKHRyYWluX2NjYSREaWFnbm9zaXMpDQpzZXQuc2VlZCgzNDUpICNmb3IgcmVwcm9kdWNpYmlsaXR5DQoNCiNGaXQgbW9kZWwgd2l0aCB0cmFpbmluZyBkYXRhIGFuZCByZXZpZXcgZGV0YWlscw0KbW9kZWxfc3ZtMSA8LSBzdm0oeDEsIHkxLCBrZXJuZWw9J3JhZGlhbCcpDQpwcmludChtb2RlbF9zdm0xKQ0Kc3VtbWFyeShtb2RlbF9zdm0xKQ0KDQojdGVzdCB3aXRoIHRlc3QgZGF0YQ0KeDF0IDwtIHRlc3RfY2NhWywtOV0NCnkxdCA8LWFzLmZhY3Rvcih0ZXN0X2NjYSREaWFnbm9zaXMpDQpwcmVkX3kxdCA8LSBwcmVkaWN0KG1vZGVsX3N2bTEsIHgxdCkNCg0KI2NoZWNrIGFjY3VyYWN5DQp0YWJsZShwcmVkX3kxdCwgeTF0KQ0KY29uZnVzaW9uTWF0cml4KHByZWRfeTF0LCB5MXQsIG1vZGU9ImV2ZXJ5dGhpbmciKQ0KYGBgDQojIyMgV2hpY2ggTW9kZWwgUHJvZHVjZWQgdGhlIEJlc3QgUmVzdWx0cz8NClRoZSBmb2xsb3dpbmcgY29kZSBibG9jayBjYXB0dXJlcyB0aGUgbW9kZWwgd2hpY2ggcHJvZHVjZWQgdGhlIGJlc3QgcmVzdWx0cywgYmFzZWQgb24gS2FwcGEgYW5kIGNvcmUgbWV0cmljIChTZW5zaXRpdml0eSkgYXMgdGhpcyBpcyBhIG1pbm9yaXR5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW0uDQpgYGB7cn0NCiMgc2V0IG1vZGVsIHZhcmlhYmxlIGZvciBiZXN0IHBlcmZvcm1hbmNlIGFzIE1JIG9yIENDQQ0KYmV0dGVyX21vZGVsID0gJ01JJw0KYGBgDQoNCg0KQ29tcGxldGUgY2FzZSBhbmFseXNpcyBwcm9kdWNlZCBhbiBleGNlbGxlbnQgbW9kZWwgYWxzbywgd2l0aCBhIGxvd2VyIEthcHBhIHJhdGUgYW5kIG1ldHJpY3MgdGhhbiB0aGUgYmFzZWxpbmUgTUkgbW9kZWwuIEFjY3VyYWN5LCBiYWxhbmNlZCBhY2N1cmFjeSBhbmQgdGhlIEYxIHNjb3JlIGFyZSBhbGwgc2xpZ2h0bHkgbG93ZXIuIEFsdGhvdWdoIHRoZSBNQ0FSIHRlc3QgcmVzdWx0IGluZGljYXRlZCB0aGF0IGRhdGEgaXMgTUNBUiwgdGhlIG1pc3NpbmcgZGF0YSBwYXR0ZXJuIGluIG9uZSB2YXJpYWJsZSBvbmx5IHN1Z2dlc3RlZCBNQVIgb3IgTU5BUi4gVGhlIGJldHRlciBwZXJmb3JtYW5jZSBvZiB0aGUgTUkgbW9kZWwgb3ZlciBDQ0EsIGRlc3BpdGUgYSBsb3cgbWlzc2luZ25lc3MgcmF0ZSwgc3VnZ2VzdHMgdGhhdCBNQVIgb3IgTU5BUiBpcyBtb3JlIGxpa2VseSBvciB0aGF0IHZhbHVhYmxlIGluZm9ybWF0aW9uIGlzIGluY2x1ZGVkIGluIHRoZSBkZWxldGVkIGRhdGEuIFRvIHRlc3QgTU5BUiBmdXJ0aGVyLCB0aGUgU1ZNIG1vZGVsIHdpbGwgYmUgdGVzdGVkIGFnYWluc3QgdGhlIGhpZ2hlc3QgZGVsdGEgYWRqdXN0ZWQgaW1wdXRhdGlvbiB0byBzZWUgaWYgdGhlcmUgaXMgYSBiaWcgaW1wYWN0IG9uIHJlc3VsdHMuIElmIENDQSBwcm9kdWNlcyBhIGJldHRlciByZXN1bHQsIHRoaXMgbWF5IGJlIGR1ZSB0byBkYXRhIGJpYXMuDQoNCiMjIyBEb2VzIEhpZ2hlc3QgRGVsdGEgQWRqdXN0bWVudCBIYXZlIEJpZyBJbXBhY3Qgb24gUmVzdWx0cz8NCg0KYGBge3J9DQojQ3JlYXRlIHggYW5kIHkgdmFsdWVzDQojZGZjcF9jY2EgZGF0YQ0KeDUgPC0gdHJhaW5fZDVbLC05XQ0KeTUgPC0gYXMuZmFjdG9yKHRyYWluX2Q1JERpYWdub3NpcykNCnNldC5zZWVkKDM0NSkgI2ZvciByZXByb2R1Y2liaWxpdHkNCg0KI0ZpdCBtb2RlbCB3aXRoIHRyYWluaW5nIGRhdGEgYW5kIHJldmlldyBkZXRhaWxzDQptb2RlbF9zdm01IDwtIHN2bSh4NSwgeTUsIGtlcm5lbD0ncmFkaWFsJykNCnByaW50KG1vZGVsX3N2bTUpDQpzdW1tYXJ5KG1vZGVsX3N2bTUpDQoNCiN0ZXN0IHdpdGggdGVzdCBkYXRhDQp4NXQgPC0gdGVzdF9kNVssLTldDQp5NXQgPC1hcy5mYWN0b3IodGVzdF9kNSREaWFnbm9zaXMpDQpwcmVkX3k1dCA8LSBwcmVkaWN0KG1vZGVsX3N2bTUsIHg1dCkNCg0KI2NoZWNrIGFjY3VyYWN5DQp0YWJsZShwcmVkX3k1dCwgeTV0KQ0KY29uZnVzaW9uTWF0cml4KHByZWRfeTV0LCB5NXQsIG1vZGU9ImV2ZXJ5dGhpbmciKQ0KYGBgDQoNClRoZXJlIGlzIG5vdCBhIGJpZyBpbXBhY3QgaW4gdGhpcyBleGFtcGxlLCBzdWdnZXN0aW5nIHJlc3VsdHMgYXJlIHJlbGF0aXZlbHkgc3RhYmxlLCB0aGF0IG11bHRpcGxlIGltcHV0YXRpb24gcHJvZHVjZXMgdGhlIGJlc3QgcmVzdWx0cyBhbmQgdGhhdCBhIHBhdHRlcm4gbWl4dHVyZSBtb2RlbCBpcyBub3QgbmVjZXNzYXJ5LiBUaGUgaW1wYWN0IHJlc3VsdCBpcyBjYXB0dXJlZCBhcyBhIFllcyBvciBObyByZXNwb25zZSBpbiB0aGUgY29kZSBiZWxvdy4NCmBgYHtyfQ0KI0lzIHRoZXJlIGEgYmlnIGltcGFjdCBvbiByZXN1bHQ/DQpkZWx0YV9pbXBhY3QgPSAnTm8nDQpgYGANCg0KDQojIE9wdGlvbmFsIFN0ZXA6IE1pc2NsYXNzaWZpZWQgQW5hbHlzaXMNCkxvb2sgYXQgd2hpY2ggcm93cyB3ZXJlIG1pc2NsYXNzaWZpZWQgYnkgdGhlIE1JIGFuZCBDQ0EgbW9kZWxzLg0KDQpgYGB7cn0NCnRlc3RfY2NhIDwtYXMuZGF0YS5mcmFtZSh0ZXN0X2NjYSkNCm1pc2NsYXNzX21pIDwtIHdoaWNoKHByZWRfeXQgIT0gdGVzdF9taVssOV0pDQptaXNjbGFzc19jY2EgPC0gd2hpY2gocHJlZF95MXQgIT0gdGVzdF9jY2FbLDldKQ0KYGBgDQoNClByaW50IG1pc2NsYXNzaWZpZWQgcm93IGZyb20gTUkgYW5kIENDQSBNb2RlbCByZXN1bHRzLg0KDQpgYGB7cn0NCnByaW50KG1pc2NsYXNzX21pKQ0KcHJpbnQobWlzY2xhc3NfY2NhKQ0KYGBgDQoNCkNyZWF0ZSBvYmplY3RzIHdpdGggbWlzY2xhc3NpZmllZCByb3dzIGFuZCBwcmludCByZXN1bHRzLg0KDQpgYGB7cn0NCiN0aGUgbW9kZWwgbWlzY2xhc3NpZmllZCB0aGUgZm9sbG93aW5nIHJvd3MNCiNzdWJzZXQgZGF0YQ0KbWlzY2xhc3Nfcm93c19taSA8LSB0ZXN0X21pW2MoMSwyLDYxLDcwLDk5LDE5NyksXQ0KbWlzY2xhc3Nfcm93c19jY2EgPC0gdGVzdF9jY2FbYygxLDIsMTUsNDMsNTgsNjcsNzksOTYsMTM1KSxdDQoNCnByaW50KG1pc2NsYXNzX3Jvd3NfbWkpDQpwcmludChtaXNjbGFzc19yb3dzX2NjYSkNCmBgYA0KDQpXaGlsc3QgNSBvZiB0aGUgNiBtaXNjbGFzc2lmaWVkIHJlc3VsdHMgZnJvbSB0aGUgbXVsdGlwbGUgaW1wdXRhdGlvbiBtb2RlbCBhbHNvIGFwcGVhciBpbiB0aGUgbW9kZWwgdXNpbmcgQ0NBLCB0aGVyZSBpcyBzdGlsbCByb29tIGZvciBpbXByb3ZlbWVudC4gSG93ZXZlciwgc2V2ZXJhbCBvZiB0aGUgbWlzY2xhc3NpZmllZCByZXN1bHRzIGhhdmUgZGF0YSB0aGF0IGFyZSBtb3JlIHJlcHJlc2VudGF0aXZlIG9mIHRoZSBvdGhlciBkaWFnbm9zaXMgLSBzdWNoIGFzIGhpZ2hlciBtZWFzdXJlcyBmb3IgQ2VsbFNoYXBlIGFuZCBCYXJlTnVjbGVpLg0KDQojIyBGaW5hbCBWaXN1YWxpc2F0aW9uIG9mIFJlc3VsdHMNCg0KYGBge3J9DQpsaWJyYXJ5KFJPQ1IpICMgZm9yIFJPQyBjdXJ2ZQ0KI2NhbGN1bGF0aW9ucyBmb3IgUk9DIGN1cnZlDQpwcmVkaWN0aW9ucyA8LSBhcy5udW1lcmljKHByZWRpY3QobW9kZWxfc3ZtLCB0ZXN0X21pWywtOV0sIHR5cGU9InJlc3BvbnNlIikpDQpwcmVkIDwtIHByZWRpY3Rpb24ocHJlZGljdGlvbnMsIHRlc3RfbWkkRGlhZ25vc2lzKQ0KcGVyZiA8LXBlcmZvcm1hbmNlKHByZWQsIG1lYXN1cmU9InRwciIsIHgubWVhc3VyZT0iZnByIikNCnBsb3QocGVyZiwgY29sPSJkb2RnZXJibHVlMSIsIG1haW49IlJPQyBDdXJ2ZSBTaG93cyBHb29kIFNlcGFyYXRpb24gb2YgRGF0YSIpDQpgYGANCiMgU3VtbWFyeSBhbmQgUmVjb21tZW5kYXRpb25zDQpUaGUgZm9sbG93aW5nIGNvZGUgd2lsbCBwcmludCBhIHN1bW1hcnksIGJhc2VkIG9uIHRoZSB2YXJpYWJsZSBpbmZvcm1hdGlvbiByZWNvcmRlZCBpbiBlYXJsaWVyIGNvZGUgYmxvY2tzLiANCg0KIyMjIFZhcmlhYmxlIFBhcmFtZXRlcnMgU2V0IEluIEV4cGVyaW1lbnQNCmBgYHtyfQ0KcHJpbnQocGFzdGUobWlzc2luZ25lc3MsICJpcyBtaXNzaW5nIGRhdGEgbGV2ZWwuIikpDQpwcmludChwYXN0ZSgiSXMgZGF0YSBtb3JlIG1vcmUgbGlrZWx5IHRvIGJlIG1pc3NpbmcgaW4gc29tZSB2YXJpYWJsZXMgdGhhbiBvdGhlcnM/IiwgbGlrZWxpaG9vZCkpDQpwcmludChwYXN0ZSgiSXMgZGF0YSBtaXNzaW5nIGZyb20gZGVwZW5kZW50IHZhcmlhYmxlKHMpIG9ubHk/IiwgZGVwZW5kZW50X29ubHkpKQ0KcHJpbnQocGFzdGUoIkRvZXMgaGlnaGVyIGRlbHRhIGFkanVzdG1lbnQgaGF2ZSBiaWcgaW1wYWN0IG9uIHJlc3VsdHM/IiwgZGVsdGFfaW1wYWN0KSkNCmBgYA0KDQoNCiMjIyBJcyBEYXRhIE1DQVIgb3IgTUFSL01OQVI/DQpgYGB7cn0NCg0KaWYgKGxpa2VsaWhvb2Q9PSJObyIgJiBtY2FyX3ByZXN1bHQ9PSJlcnJvciIpIHsNCiAgIGh5cG90aGVzaXM9Ik1pc3NpbmcgZGF0YSBwYXR0ZXJuIHByb3ZpZGVzIHNvbWUgc3VwcG9ydCBmb3IgTUNBUiBoeXBvdGhlc2lzLiBIb3dldmVyLCBubyByZXN1bHQgYXZhaWxhYmxlIGZvciBNQ0FSIFRlc3QuIg0KICAgfSBlbHNlIGlmIChsaWtlbGlob29kPT0iTm8iICYgbWNhcl9wcmVzdWx0PDAuMDUpIHsNCiAgIGh5cG90aGVzaXM9Ikh5cG90aGVzaXMgdW5wcm92ZW4uIE1DQVIgVGVzdCBhbmQgbWlzc2luZyBkYXRhIHBhdHRlcm4gaW5kaWNhdGUgZGlmZmVyZW50IG1pc3NpbmcgZGF0YSBtZWNoYW5pc21zLiINCiAgIH0gZWxzZSBpZiAobGlrZWxpaG9vZD09Ik5vIiAmIG1jYXJfcHJlc3VsdD49MC4wNSkgew0KICAgICBoeXBvdGhlc2lzPSJNQ0FSIGh5cG90aGVzaXMgc3VwcG9ydGVkIGJ5IE1DQVIgVGVzdCBhbmQgbWlzc2luZyBkYXRhIHBhdHRlcm4uIg0KICAgfSBlbHNlIGlmKGxpa2VsaWhvb2Q9PSJZZXMiICYgbWNhcl9wcmVzdWx0PT0iZXJyb3IiKSB7DQogICAgIGh5cG90aGVzaXM9Ik1pc3NpbmcgZGF0YSBwYXR0ZXJuIHByb3ZpZGVzIHNvbWUgc3VwcG9ydCBmb3IgTUFSL01OQVIgaHlwb3RoZXNpcy4gSG93ZXZlciwgbm8gcmVzdWx0IGF2YWlsYWJsZSBmb3IgTUNBUiBUZXN0LiINCiAgIH0gZWxzZSBpZiAobGlrZWxpaG9vZD09IlllcyIgJiBtY2FyX3ByZXN1bHQ8MC4wNSkgew0KICAgICBoeXBvdGhlc2lzPSJNQVIvTU5BUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCBieSBNQ0FSIFRlc3QgYW5kIG1pc3NpbmcgZGF0YSBwYXR0ZXJuLiINCiAgIH1lbHNlIGlmIChsaWtlbGlob29kPT0iWWVzIiAmIG1jYXJfcHJlc3VsdD49MC4wNSkgew0KICAgICBoeXBvdGhlc2lzPSJIeXBvdGhlc2lzIHVucHJvdmVuLiBNQ0FSIFRlc3QgYW5kIG1pc3NpbmcgZGF0YSBwYXR0ZXJuIGluZGljYXRlIGRpZmZlcmVudCBtaXNzaW5nIGRhdGEgbWVjaGFuaXNtcy4iDQogICB9ZWxzZSB7DQogICBwcmludCgiTm8gaHlwb3RoZXNpcyBmb3VuZC4iKQ0KfQ0KDQpwcmludChoeXBvdGhlc2lzKQ0KYGBgDQoNCiMjIyBSZWNvbW1lbmRlZCBBcHByb2FjaCBmb3IgTWlzc2luZyBEYXRhDQpgYGB7cn0NCiNkZWZpbmUgZGlmZmVyZW50IHJlY29tbWVuZGVkIGFwcHJvYWNoZXMNCmRhdGFfYXBwcm9hY2gxID0gIkR1ZSB0byBvdmVyYWxsIGxldmVsIG9mIG1pc3NpbmcgZGF0YSAob3IgbGV2ZWwgb2YgbWlzc2luZyBkYXRhIGZyb20gZGVwZW5kZW50IHZhcmlhYmxlIG9ubHkpIHRoZXJlIGlzIGEgcmlzayB0aGF0IGEgcG9vciBtb2RlbCB3aWxsIGJlIHJldHVybmVkLCByZWdhcmRsZXNzIG9mIG1ldGhvZCB1c2VkLCB0aGF0IHdvdWxkIG5vdCBnZW5lcmFsaXNlIHdlbGwgb24gbmV3IGRhdGEuIg0KDQpkYXRhX2FwcHJvYWNoMiA9ICJBcyBNQ0FSIGh5cG90aGVzaXMgaXMgc3VwcG9ydGVkIGFuZCBtaXNzaW5nIGRhdGEgaXMgbGVzcyB0aGFuIDIwJSwgY29tcGxldGUgY2FzZSBhbmFseXNpcyBpcyBsaWtlbHkgdG8gcHJvZHVjZSB1bmJpYXNlZCByZXN1bHRzLiBIb3dldmVyLCBtdWx0aXBsZSBpbXB1dGF0aW9uIG1heSBwcm9kdWNlIGEgbW9yZSBlZmZlY3RpdmUgbW9kZWwgaW4gc29tZSBjaXJjdW1zdGFuY2VzLiINCg0KZGF0YV9hcHByb2FjaDMgPSAiTUNBUiBoeXBvdGhlc2lzIGlzIHN1cHBvcnRlZC4gSG93ZXZlciwgYXMgbWlzc2luZyBkYXRhIGlzIGJldHdlZW4gMjAtNTAlLCBtdWx0aXBsZSBpbXB1dGF0aW9uIGlzIGxpa2VseSB0byBwcm9kdWNlIGEgbW9yZSBlZmZlY3RpdmUgbW9kZWwuIg0KDQpkYXRhX2FwcHJvYWNoNCA9ICJNQVIvTU5BUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCwgb3IgTUNBUiBoeXBvdGhlc2lzIHVucHJvdmVuLiBIb3dldmVyLCBhcyBtaXNzaW5nIGRhdGEgaXMgbGVzcyB0aGFuIDIwJSBhbmQgbWlzc2luZyBmcm9tIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgb25seSwgY29tcGxldGUgY2FzZSBhbmFseXNpcyBtYXkgYmUgdGhlIG1vcmUgcmVsaWFibGUgYXBwcm9hY2guIg0KDQpkYXRhX2FwcHJvYWNoNSA9ICJNQVIvTU5BUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCwgb3IgTUNBUiBoeXBvdGhlc2lzIHVucHJvdmVuLiBBbHNvLCBhcyBtaXNzaW5nIGRhdGEgaXMgbGVzcyB0aGFuIDUwJSBhbmQgdGhlIGRlbHRhIHNlbnNpdGl2aXR5IGFuYWx5c2lzIHN1Z2dlc3RzIHRoZSByZXN1bHRzIGFyZSByZWxhdGl2ZWx5IHN0YWJsZSwgbXVsdGlwbGUgaW1wdXRhdGlvbiBpcyB0aGUgcmVjb21tZW5kZWQgYXBwcm9hY2guIg0KDQpkYXRhX2FwcHJvYWNoNiA9ICJNQVIvTU5BUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCwgb3IgTUNBUiBoeXBvdGhlc2lzIHVucHJvdmVuLiBUaGUgZGVsdGEgc2Vuc2l0aXZpdHkgYW5hbHlzaXMgc3VnZ2VzdHMgdGhlIHJlc3VsdHMgYXJlIG5vdCBzdGFibGUgYW5kIGluZGljYXRpdmUgb2YgTU5BUiBkYXRhLiBUaGVyZWZvcmUsIGEgcGF0dGVybiBtaXh0dXJlIG1vZGVsIGlzIHJlY29tbWVuZGVkIGZvciBmdXJ0aGVyIGNvbnNpZGVyYXRpb24uIFRoZXJlIGFyZSBhIGNvdXBsZSBvZiBSIG1pY2UgcGFja2FnZSBmdW5jdGlvbnMgd29ydGggZXhwbG9yaW5nIGZ1cnRoZXIgZm9yIHRoaXM6IG1pY2UuaW1wdXRlLnJpIGFuZCBtaWNlLmltcHV0ZS5tbmFyLmxvZ3JlZy4gKHZhbiBCdXVyZW4gZXQgYWwuLCAyMDIzKSINCg0KI2RlZmluZSBjb25kaXRpb25hbCBzdGF0ZW1lbnRzDQppZiAobWlzc2luZ25lc3M+PTAuNSkgew0KICAgYXBwcm9hY2g9ZGF0YV9hcHByb2FjaDENCiAgIH0gZWxzZSBpZiAobWlzc2luZ25lc3M+MC4yICYgZGVwZW5kZW50X29ubHk9PSdZZXMnKSB7DQogICAgIGFwcHJvYWNoPWRhdGFfYXBwcm9hY2gxDQogICB9IGVsc2UgaWYgKGh5cG90aGVzaXM9PSJNQ0FSIGh5cG90aGVzaXMgc3VwcG9ydGVkIGJ5IE1DQVIgVGVzdCBhbmQgbWlzc2luZyBkYXRhIHBhdHRlcm4uIiAmIG1pc3NpbmduZXNzPD0wLjIpIHsNCiAgICAgICBhcHByb2FjaD1kYXRhX2FwcHJvYWNoMiANCiAgIH0gZWxzZSBpZiAoaHlwb3RoZXNpcz09Ik1DQVIgaHlwb3RoZXNpcyBzdXBwb3J0ZWQgYnkgTUNBUiBUZXN0IGFuZCBtaXNzaW5nIGRhdGEgcGF0dGVybi4iICYgbWlzc2luZ25lc3M+MC4yICYgbWlzc2luZ25lc3M8MC41KSB7DQogICAgIGFwcHJvYWNoPWRhdGFfYXBwcm9hY2gzIA0KICAgfSBlbHNlIGlmKGh5cG90aGVzaXMhPSJNQ0FSIGh5cG90aGVzaXMgc3VwcG9ydGVkIGJ5IE1DQVIgVGVzdCBhbmQgbWlzc2luZyBkYXRhIHBhdHRlcm4uIiAmIG1pc3NpbmduZXNzPD0wLjIgJiBkZXBlbmRlbnRfb25seT09IlllcyIpIHsNCiAgICAgYXBwcm9hY2g9ZGF0YV9hcHByb2FjaDQNCiAgIH0gZWxzZSBpZihoeXBvdGhlc2lzIT0iTUNBUiBoeXBvdGhlc2lzIHN1cHBvcnRlZCBieSBNQ0FSIFRlc3QgYW5kIG1pc3NpbmcgZGF0YSBwYXR0ZXJuLiIgJiBtaXNzaW5nbmVzczw9MC41ICYgZGVsdGFfaW1wYWN0PT0iTm8iKSB7DQogICAgIGFwcHJvYWNoPWRhdGFfYXBwcm9hY2g1DQogICB9IGVsc2UgaWYoaHlwb3RoZXNpcyE9Ik1DQVIgaHlwb3RoZXNpcyBzdXBwb3J0ZWQgYnkgTUNBUiBUZXN0IGFuZCBtaXNzaW5nIGRhdGEgcGF0dGVybi4iICAmIGRlbHRhX2ltcGFjdD09IlllcyIpIHsNCiAgICAgYXBwcm9hY2g9ZGF0YV9hcHByb2FjaDYNCiAgIH1lbHNlIHsNCiAgIHByaW50KCJObyBhcHByb2FjaCBmb3VuZC4iKQ0KfQ0KDQpwcmludChhcHByb2FjaCkNCg0KYGBgDQoNCiMjIyBDb25zaWRlcmF0aW9uIG9mIFRlc3QgTW9kZWwgUmVzdWx0cyBpbiBEYXRhIEV4cGVyaW1lbnQNCmBgYHtyfQ0KDQppZiAoYmV0dGVyX21vZGVsPT0iTUkiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2gxKSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIE11bHRpcGxlIEltcHV0YXRpb24gcHJvZHVjZWQgYSBtb3JlIGVmZmVjdGl2ZSBtb2RlbCB0aGFuIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMuIEhvd2V2ZXIsIHRoZXJlIGlzIGEgcmlzayB0aGF0IGl0IHdvdWxkIG5vdCBnZW5lcmFsaXNlIHdlbGwgb24gbmV3IGRhdGEuIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iQ0NBIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoMSkgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzIHByb2R1Y2VkIHRoZSBtb3N0IGVmZmVjdGl2ZSBtb2RlbC4gSG93ZXZlciwgdGhlcmUgaXMgYSByaXNrIHRoYXQgaXQgd291bGQgbm90IGdlbmVyYWxpc2Ugd2VsbCBvbiBuZXcgZGF0YS4iKQ0KICAgfSBlbHNlIGlmIChiZXR0ZXJfbW9kZWw9PSJNSSIgJiBhcHByb2FjaD09ZGF0YV9hcHByb2FjaDIpIHsNCiAgIHByaW50KCJGb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEgZXhwZXJpbWVudCwgTXVsdGlwbGUgSW1wdXRhdGlvbiBwcm9kdWNlZCBhIG1vcmUgZWZmZWN0aXZlIG1vZGVsIHRoYW4gY29tcGxldGUgY2FzZSBhbmFseXNpcy4gSG93ZXZlciwgY29tcGxldGUgY2FzZSBhbmFseXNpcyB3b3VsZCBiZSBsaWtlbHkgdG8gcHJvZHVjZSB1bmJpYXNlZCByZXN1bHRzIGFsc28uIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iQ0NBIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoMikgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzIHByb2R1Y2VkIHRoZSBtb3N0IGVmZmVjdGl2ZSBtb2RlbC4gSG93ZXZlciwgbXVsdGlwbGUgaW1wdXRhdGlvbiBtYXkgcHJvZHVjZSBhIG1vcmUgZWZmZWN0aXZlIG1vZGVsIGluIHNvbWUgY2lyY3Vtc3RhbmNlcy4iKQ0KICAgfSBlbHNlIGlmIChiZXR0ZXJfbW9kZWw9PSJNSSIgJiBhcHByb2FjaD09ZGF0YV9hcHByb2FjaDMpIHsNCiAgIHByaW50KCJGb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEgZXhwZXJpbWVudCwgTXVsdGlwbGUgSW1wdXRhdGlvbiBwcm9kdWNlZCBhIG1vcmUgZWZmZWN0aXZlIG1vZGVsIHRoYW4gY29tcGxldGUgY2FzZSBhbmFseXNpcy4gVGhpcyByZXN1bHQgaXMgZXhwZWN0ZWQsIGdpdmVuIHZhcmlhYmxlcyBwcm92aWRlZC4iKQ0KICAgfSBlbHNlIGlmIChiZXR0ZXJfbW9kZWw9PSJDQ0EiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2gzKSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgcHJvZHVjZWQgdGhlIG1vc3QgZWZmZWN0aXZlIG1vZGVsLiBIb3dldmVyLCBjYXV0aW9uIHNob3VsZCBiZSBub3RlZCBvdmVyIHJlc3VsdHMgZHVlIHRvIGhpZ2ggbGV2ZWwgb2YgbWlzc2luZ25lc3MuIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iTUkiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2g0KSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIE11bHRpcGxlIEltcHV0YXRpb24gcHJvZHVjZWQgYSBtb3JlIGVmZmVjdGl2ZSBtb2RlbCB0aGFuIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMuIEhvd2V2ZXIsIGNhdXRpb24gc2hvdWxkIGJlIHRha2VuIHdpdGggaG93IG1pc3NpbmcgZGF0YSBpbiBkZXBlbmRlbnQgdmFyaWFibGVzIGlzIGhhbmRsZWQsIGFzIENDQSBtYXkgYmUgbW9yZSByZWxpYWJsZS4iKQ0KICAgfSBlbHNlIGlmIChiZXR0ZXJfbW9kZWw9PSJDQ0EiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2g0KSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMgcHJvZHVjZWQgdGhlIG1vc3QgZWZmZWN0aXZlIG1vZGVsLiBUaGlzIHNob3VsZCBiZSBhIHJlbGlhYmxlIGFwcHJvYWNoIGZvciB0aGlzIG1pc3NpbmcgZGF0YSBwcm9ibGVtLiIpDQogICB9IGVsc2UgaWYgKGJldHRlcl9tb2RlbD09Ik1JIiAmIGFwcHJvYWNoPT1kYXRhX2FwcHJvYWNoNSkgew0KICAgcHJpbnQoIkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YSBleHBlcmltZW50LCBNdWx0aXBsZSBJbXB1dGF0aW9uIHByb2R1Y2VkIGEgbW9yZSBlZmZlY3RpdmUgbW9kZWwgdGhhbiBjb21wbGV0ZSBjYXNlIGFuYWx5c2lzLiBUaGlzIHNob3VsZCBiZSBhIHJlbGlhYmxlIGFwcHJvYWNoIGZvciB0aGlzIG1pc3NpbmcgZGF0YSBwcm9ibGVtLiIpDQogICB9IGVsc2UgaWYgKGJldHRlcl9tb2RlbD09IkNDQSIgJiBhcHByb2FjaD09ZGF0YV9hcHByb2FjaDUpIHsNCiAgIHByaW50KCJGb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEgZXhwZXJpbWVudCwgY29tcGxldGUgY2FzZSBhbmFseXNpcyBwcm9kdWNlZCB0aGUgbW9zdCBlZmZlY3RpdmUgbW9kZWwuIEhvd2V2ZXIsIHRoZSBNQ0FSIGh5cG90aGVzaXMgaXMgZWl0aGVyIG5vdCBzdXBwb3J0ZWQgb3IgdW5jbGVhciBzbyBjYXV0aW9uIGlzIGFkdmlzZWQgb24gdGhlIHJlc3VsdHMgcHJvZHVjZWQuIikNCiAgIH0gZWxzZSBpZiAoYmV0dGVyX21vZGVsPT0iTUkiICYgYXBwcm9hY2g9PWRhdGFfYXBwcm9hY2g2KSB7DQogICBwcmludCgiRm9yIHRoaXMgcGFydGljdWxhciBkYXRhIGV4cGVyaW1lbnQsIE11bHRpcGxlIEltcHV0YXRpb24gcHJvZHVjZWQgYSBtb3JlIGVmZmVjdGl2ZSBtb2RlbCB0aGFuIGNvbXBsZXRlIGNhc2UgYW5hbHlzaXMuIEhvd2V2ZXIsIGl0IGlzIHJlY29tbWVuZGVkIHRoYXQgYSBwYXR0ZXJuIG1peHR1cmUgbW9kZWwgaXMgY29uc2lkZXJlZCBhbHNvLiIpDQogICB9IGVsc2UgaWYgKGJldHRlcl9tb2RlbD09IkNDQSIgJiBhcHByb2FjaD09ZGF0YV9hcHByb2FjaDYpIHsNCiAgIHByaW50KCJGb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEgZXhwZXJpbWVudCwgY29tcGxldGUgY2FzZSBhbmFseXNpcyBwcm9kdWNlZCB0aGUgbW9zdCBlZmZlY3RpdmUgbW9kZWwuIEhvd2V2ZXIsIGl0IGlzIHJlY29tbWVuZGVkIHRoYXQgYSBwYXR0ZXJuIG1peHR1cmUgbW9kZWwgaXMgY29uc2lkZXJlZCBhbHNvLiIpDQogICB9IGVsc2Ugew0KICAgcHJpbnQoIk5vIG1vZGVsIGV2YWx1YXRpb24gZm91bmQuIikNCn0NCmBgYA0KDQoNCg0KDQojIFJlZmVyZW5jZXMNCi0gIHZhbiBCdXVyZW4sIFMuIGV0IGFsLiAoMjAyMykg4oCYbWljZTogTXVsdGl2YXJpYXRlIEltcHV0YXRpb24gYnkgQ2hhaW5lZCBFcXVhdGlvbnPigJkuIENSQU4uIEF2YWlsYWJsZSBhdDogaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1taWNlIChBY2Nlc3NlZDogMTAgQXVndXN0IDIwMjMpLg0KLSBTYWxsYW4sIEouTS4gKDIwMjApIENvbXBhcmluZyBTVk0ga2VybmVscywgcnB1YnMuY29tLiBBdmFpbGFibGUgYXQ6IGh0dHBzOi8vcnB1YnMuY29tL2ptc2FsbGFuL1NWTWtlcm5lbHMgKEFjY2Vzc2VkOiA2IEF1Z3VzdCAyMDIzKS4NCi0gVmluaywgRy4gYW5kIHZhbiBCdXVyZW4sIFMuIChubyBkYXRlKSBtaWNlOiBBbiBhcHByb2FjaCB0byBzZW5zaXRpdml0eSBhbmFseXNpcywgd3d3Lmdlcmtvdmluay5jb20uIEF2YWlsYWJsZSBhdDogaHR0cHM6Ly93d3cuZ2Vya292aW5rLmNvbS9taWNlVmlnbmV0dGVzL1NlbnNpdGl2aXR5X2FuYWx5c2lzL1NlbnNpdGl2aXR5X2FuYWx5c2lzLmh0bWwgKEFjY2Vzc2VkOiA1IEF1Z3VzdCAyMDIzKS4NCi0gV29sYmVyZywgV2kuICgxOTkyKSBCcmVhc3QgQ2FuY2VyIFdpc2NvbnNpbiAoT3JpZ2luYWwpLCBVQ0kgTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5LiBBdmFpbGFibGUgYXQ6IGh0dHBzOi8vZG9pLm9yZy8xMC4yNDQzMi9DNUhQNFouDQoNCi0tTGFzdCB1cGRhdGVkOiBBdWd1c3QgMjAyMyAtLSAgDQoNCi0tRW5kLS0=