1 Introduce the datasets

The data table is the result from an cardiovascular (CV) study in Cleveland Clinic between May 1981 and September 1984. No patients had a history of CV disease. After providing the historical information, all patients performed a number of clinical tests. A part of features from these results was collected in the studying data table.

3 Exploratory Data

The data have 303 rows or patients, and 15 fields: X, Age, Sex, ChestPain, RestBP, Chol, Fbs, RestECG, MaxHR, ExAng, Oldpeak, Slope, Ca, Thal, AHD. Because the first column is X - the ID of patients, it’s not necessary for this report, so I’m going to remove it.

## 'data.frame':    303 obs. of  14 variables:
##  $ Age      : int  63 67 67 37 41 56 62 57 63 53 ...
##  $ Sex      : Factor w/ 2 levels "0","1": 2 2 2 2 1 2 1 1 2 2 ...
##  $ ChestPain: Factor w/ 4 levels "4","3","2","1": 4 1 1 2 3 3 1 1 1 1 ...
##  $ RestBP   : int  145 160 120 130 130 120 140 120 130 140 ...
##  $ Chol     : int  233 286 229 250 204 236 268 354 254 203 ...
##  $ Fbs      : Factor w/ 2 levels "0","1": 2 1 1 1 1 1 1 1 1 2 ...
##  $ RestECG  : Factor w/ 3 levels "0","1","2": 3 3 3 1 3 1 3 1 3 3 ...
##  $ MaxHR    : int  150 108 129 187 172 178 160 163 147 155 ...
##  $ ExAng    : Factor w/ 2 levels "0","1": 1 2 2 1 1 1 1 2 1 2 ...
##  $ Oldpeak  : num  2.3 1.5 2.6 3.5 1.4 0.8 3.6 0.6 1.4 3.1 ...
##  $ Slope    : Factor w/ 3 levels "1","2","3": 3 2 2 3 1 1 3 1 2 3 ...
##  $ Ca       : Factor w/ 4 levels "0","1","2","3": 1 4 3 1 1 1 3 1 2 1 ...
##  $ Thal     : Factor w/ 3 levels "1","2","3": 2 1 3 1 1 1 1 1 3 3 ...
##  $ AHD      : Factor w/ 2 levels "1","0": 2 1 1 2 2 2 1 2 1 1 ...

The basic statistics of data was below :

##       Age        Sex     ChestPain     RestBP           Chol       Fbs    
##  Min.   :29.00   0: 97   4:144     Min.   : 94.0   Min.   :126.0   0:258  
##  1st Qu.:48.00   1:206   3: 86     1st Qu.:120.0   1st Qu.:211.0   1: 45  
##  Median :56.00           2: 50     Median :130.0   Median :241.0          
##  Mean   :54.44           1: 23     Mean   :131.7   Mean   :246.7          
##  3rd Qu.:61.00                     3rd Qu.:140.0   3rd Qu.:275.0          
##  Max.   :77.00                     Max.   :200.0   Max.   :564.0          
##  RestECG     MaxHR       ExAng      Oldpeak     Slope      Ca        Thal    
##  0:151   Min.   : 71.0   0:204   Min.   :0.00   1:142   0   :176   1   :166  
##  1:  4   1st Qu.:133.5   1: 99   1st Qu.:0.00   2:140   1   : 65   2   : 18  
##  2:148   Median :153.0           Median :0.80   3: 21   2   : 38   3   :117  
##          Mean   :149.6           Mean   :1.04           3   : 20   NA's:  2  
##          3rd Qu.:166.0           3rd Qu.:1.60           NA's:  4             
##          Max.   :202.0           Max.   :6.20                                
##  AHD    
##  1:139  
##  0:164  
##         
##         
##         
## 

Because the data contain missing values and I would be removed before building model

After removing the NA, the table consisted of 172 rows of patients.

Because the AHD is a categorical variable with two values : Yes & No, so I’m using boxplot to show the diffirence of AHD with continuous variables.

Base on the above charts, the boxplot show the difference in distribution of AHD by Age, MaxHR and Oldpeak. These hypothesis would be tested by t-test.

4 Hypothesis testing

## 
##  Welch Two Sample t-test
## 
## data:  Age by AHD
## t = 4.0636, df = 294.66, p-value = 6.204e-05
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  2.122234 6.108514
## sample estimates:
## mean in group 1 mean in group 0 
##        56.75912        52.64375
## 
##  Welch Two Sample t-test
## 
## data:  MaxHR by AHD
## t = -7.9286, df = 266.44, p-value = 6.108e-14
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -24.30715 -14.63637
## sample estimates:
## mean in group 1 mean in group 0 
##        139.1095        158.5813
## 
##  Welch Two Sample t-test
## 
## data:  Oldpeak by AHD
## t = 7.7558, df = 216, p-value = 3.429e-13
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  0.738632 1.241970
## sample estimates:
## mean in group 1 mean in group 0 
##        1.589051        0.598750

The t.test show there was a significant difference in mean of Age, MaxHR, Oldpeak between group of patiend who had AHD or no AHD.

5 Build neural network model

5.1 Pre-process data

So let’s begin to build a neural network model. In this report, I would be built a model neural network of logistic regression with AHD variable in order to predict patients with heart disease or not? (corresponding to two values yes or no).

First, I used sample.split() function to separate data into two parts : training data and testing data.

In this above lines code, I created y_train and y_test variables. Both were converted to numeric data type in order to suitable with require of model. It contains the actual result of AHD variable. y_train accounts for 80% of AHD and y_test takes up the rest of the AHD variable. Then, I created x_train and x_test corresponding y_train and y_test but in this x_train and x_test, both contain remaining 13 columns except AHD column. There are the necessary variables to predict the AHD variable.

# Create x_train 
x_train <- subset(data, split == TRUE)
x_train <- x_train[1:13]
x_train$Age <- as.numeric(x_train$Age)
x_train$Sex <- as.numeric(as.character(x_train$Sex))
x_train$ChestPain <- as.numeric(as.character(x_train$ChestPain))
x_train$RestBP <- as.numeric(x_train$RestBP)
x_train$Chol <- as.numeric(x_train$Chol)
x_train$Fbs <- as.numeric(as.character(x_train$Fbs))
x_train$RestECG <- as.numeric(as.character(x_train$RestECG))
x_train$MaxHR <- as.numeric(x_train$MaxHR)
x_train$ExAng <- as.numeric(as.character(x_train$ExAng))
x_train$Oldpeak <- as.numeric(x_train$Oldpeak)
x_train$Slope <- as.numeric(as.character(x_train$Slope))
x_train$Ca <- as.numeric(as.character(x_train$Ca))
x_train$Thal<- as.numeric(as.character(x_train$Thal))


x_train <- as.matrix(x_train)
x_train <- scale(x_train)


# Create x_test
x_test <- subset(data, split == FALSE)
x_test <- x_test[1:13]
x_test$Age <- as.numeric(x_test$Age)
x_test$Sex <- as.numeric(as.character(x_test$Sex))
x_test$ChestPain <- as.numeric(as.character(x_test$ChestPain))
x_test$RestBP <- as.numeric(x_test$RestBP)
x_test$Chol <- as.numeric(x_test$Chol)
x_test$Fbs <- as.numeric(as.character(x_test$Fbs))
x_test$RestECG <- as.numeric(as.character(x_test$RestECG))
x_test$MaxHR <- as.numeric(x_test$MaxHR)
x_test$ExAng <- as.numeric(as.character(x_test$ExAng))
x_test$Oldpeak <- as.numeric(x_test$Oldpeak)
x_test$Slope <- as.numeric(as.character(x_test$Slope))
x_test$Ca <- as.numeric(as.character(x_test$Ca))
x_test$Thal <- as.numeric(as.character(x_test$Thal))


x_test <- as.matrix(x_test)
col_means_train <- attr(x_train, "scaled:center") 
col_stddevs_train <- attr(x_train, "scaled:scale")
x_test <- scale(x_test, center = col_means_train, scale = col_stddevs_train)

Then I have x_train and x_test, there was scaled by scale() function. The x_test was scaled with mean and standard deviation of x_train. The purpose of this is to fit the model and not be overfiting.

5.2 Build neural network with AHD

After pre-processed data and created Traing data and Testing data, I started to build model and training model.

## Model: "sequential"
## ________________________________________________________________________________
## Layer (type)                        Output Shape                    Param #     
## ================================================================================
## dense (Dense)                       (None, 16)                      224         
## ________________________________________________________________________________
## dense_1 (Dense)                     (None, 1)                       17          
## ================================================================================
## Total params: 241
## Trainable params: 241
## Non-trainable params: 0
## ________________________________________________________________________________

In this above lines code, I built a model neural network. This model has two layers : Input and Output and used activation functions were relu and sigmoid. In compile model, the loss function is binary_crossentropy method, the optimization method is adam and used accurancy to evalute the accurancy model.

5.3 Training model

Let’s training model with fit() and the number of epoch are 200.

5.4 Evaluate accuracy

After training, I evaluated the loss and accurancy in testing data. The result are loss : 0.3077 and accurancy : 0.8305. Was this result OK ?

5.5 Make predictions

The output of the model is stored in the p1 variable

##             [,1]
##  [1,] 0.99867970
##  [2,] 0.09159675
##  [3,] 0.01610032
##  [4,] 0.99753481
##  [5,] 0.95574594
##  [6,] 0.98702645
##  [7,] 0.99302530
##  [8,] 0.12536159
##  [9,] 0.26144147
## [10,] 0.98831415
## [11,] 0.73331028
## [12,] 0.42922673
## [13,] 0.12150046
## [14,] 0.98800761
## [15,] 0.04571483
## [16,] 0.99111485
## [17,] 0.10394582
## [18,] 0.28652352
## [19,] 0.20704731
## [20,] 0.95554471

Finally, I used confusion matrix method to check the result again :

## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  0  1
##          0 30  3
##          1  2 24
##                                           
##                Accuracy : 0.9153          
##                  95% CI : (0.8132, 0.9719)
##     No Information Rate : 0.5424          
##     P-Value [Acc > NIR] : 5.04e-10        
##                                           
##                   Kappa : 0.8288          
##                                           
##  Mcnemar's Test P-Value : 1               
##                                           
##             Sensitivity : 0.9375          
##             Specificity : 0.8889          
##          Pos Pred Value : 0.9091          
##          Neg Pred Value : 0.9231          
##              Prevalence : 0.5424          
##          Detection Rate : 0.5085          
##    Detection Prevalence : 0.5593          
##       Balanced Accuracy : 0.9132          
##                                           
##        'Positive' Class : 0               
## 

6 Conclusion

In this report, I had built a neural network model with logistic regression. By using the adam method to optimize the loss function and using two activation functions : sigmoid and relu to build the model, the result is quite good with loss : 0.3077 and accurancy : 0.8305. However, this model was not the most optimal model but it was very useful to diagnose what a patient has heart disease or not - a good assistant for a doctor.

LS0tCnRpdGxlOiAiTmV1cmFsIG5ldHdvcmsgd2l0aCBsb2dpc3RpYyByZWdyZXNzaW9uIgphdXRob3I6ICJIdXkgRC5QaHVuZyAtICgrODQpOTg2MjQxMzA4IC0gR21haWwgOiBodXlwaHVuZzI2OEBnbWFpbC5jb20iCmRhdGU6ICI0LzI5LzIwMjAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50IDoKICAgIGNvZGVfZG93bmxvYWQgOiBUUlVFCiAgICBudW1iZXJfc2VjdGlvbiA6IFlFUwogICAgdG9jIDogWUVTCiAgICB0b2NfZmxvYXQgOiBZRVMKICAgIHRoZW1lIDogY2VydWxlYW4KICAgIGhpZ2hsaWdodCA6IHRhbmdvCiAgcGRmX2RvY3VtZW50OgogICAgbnVtYmVyX3NlY3Rpb24gOiBZRVMKICAgIHRvYyA6IFlFUwogICAgdG9jX2Zsb2F0IDogWUVTCgotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyBJbnRyb2R1Y2UgdGhlIGRhdGFzZXRzCgpUaGUgZGF0YSB0YWJsZSBpcyB0aGUgcmVzdWx0IGZyb20gYW4gY2FyZGlvdmFzY3VsYXIgKENWKSBzdHVkeSBpbiBDbGV2ZWxhbmQgQ2xpbmljIGJldHdlZW4gTWF5IDE5ODEgYW5kIFNlcHRlbWJlciAxOTg0LiBObyBwYXRpZW50cyBoYWQgYSBoaXN0b3J5IG9mIENWIGRpc2Vhc2UuIEFmdGVyIHByb3ZpZGluZyB0aGUgaGlzdG9yaWNhbCBpbmZvcm1hdGlvbiwgYWxsIHBhdGllbnRzIHBlcmZvcm1lZCBhIG51bWJlciBvZiBjbGluaWNhbCB0ZXN0cy4gQSBwYXJ0IG9mIGZlYXR1cmVzIGZyb20gdGhlc2UgcmVzdWx0cyB3YXMgY29sbGVjdGVkIGluIHRoZSBzdHVkeWluZyBkYXRhIHRhYmxlLgoKIyBEYXRhIGxvYWRpbmcKClRoZSBkYXRhc2V0IHdhcyBsb2FkZWQgYW5kIGFzc2lnbmVkIHRvIGEgdmFyaWFibGUgY2FsbCBgZGF0YWAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgTG9hZGluZyBwYWNrYWdlcyAKbGlicmFyeShkcGx5cikKbGlicmFyeShrZXJhcykKbGlicmFyeShIbWlzYykKbGlicmFyeShjYVRvb2xzKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ3JpZEV4dHJhKQoKCiMgTG9hZGluZyBkYXRhCnBhdGggPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9IdXlQaHVuZzI2OC9SZXBvcnQvbWFzdGVyL0hlYXJ0LmNzdiIKZGF0YSA8LSByZWFkLmNzdihwYXRoLCBoZWFkZXIgPSBUUlVFKQoKYGBgCgojIEV4cGxvcmF0b3J5IERhdGEgCgpUaGUgZGF0YSBoYXZlIGByIGRpbShkYXRhKVsxXWAgcm93cyBvciBwYXRpZW50cywgYW5kIGByIGRpbShkYXRhKVsyXWAgZmllbGRzOiBgciBjb2xuYW1lcyhkYXRhKWAuIEJlY2F1c2UgdGhlIGZpcnN0IGNvbHVtbiBpcyBgWGAgLSB0aGUgSUQgb2YgcGF0aWVudHMsIGl0J3Mgbm90IG5lY2Vzc2FyeSBmb3IgdGhpcyByZXBvcnQsIHNvIEknbSBnb2luZyB0byByZW1vdmUgaXQuCgpgYGB7cn0KIyBSZW1vdmUgdGhlIGZpcnN0IGNvbHVtbgpkYXRhX2NvbCA8LSBjb2xuYW1lcyhkYXRhKQpkYXRhX2NvbCA8LSBkYXRhX2NvbFtkYXRhX2NvbCAhPSAiWCJdCmRhdGEgPC0gZGF0YVtkYXRhX2NvbF0KCiMgUHJvY2VzcyB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIApkYXRhJFNleCA8LSBmYWN0b3IoZGF0YSRTZXgpICMgSW4gYHNleGAgdmFyaWFibGUsIDAgOiAnZmVtYWxlJyBhbmQgMSA6ICdtYWxlJwpkYXRhJENoZXN0UGFpbiA8LSBmYWN0b3IoZGF0YSRDaGVzdFBhaW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygnYXN5bXB0b21hdGljJywnbm9uYW5naW5hbCcsJ25vbnR5cGljYWwnLCd0eXBpY2FsJyksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDQsMywyLDEpKQpkYXRhJEZicyA8LSBmYWN0b3IoZGF0YSRGYnMpCmRhdGEkUmVzdEVDRyA8LSBmYWN0b3IoZGF0YSRSZXN0RUNHKQpkYXRhJEV4QW5nIDwtIGZhY3RvcihkYXRhJEV4QW5nKQpkYXRhJFNsb3BlIDwtIGZhY3RvcihkYXRhJFNsb3BlKQpkYXRhJENhIDwtIGZhY3RvcihkYXRhJENhKQpkYXRhJFRoYWwgPC0gZmFjdG9yKGRhdGEkVGhhbCwgbGV2ZWxzID0gYygnbm9ybWFsJywnZml4ZWQnLCdyZXZlcnNhYmxlJyksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygxLDIsMykpCmRhdGEkQUhEIDwtIGZhY3RvcihkYXRhJEFIRCwgbGV2ZWxzID0gYygnWWVzJywnTm8nKSwgbGFiZWxzID0gYygxLDApKQpzdHIoZGF0YSkKCmZhY3Rvcl92YXJpYWJsZXMgPC0gYygiU2V4IiwgIkZicyIsICJSZXN0RUNHIiwgIkV4QW5nIiwgIlNsb3BlIiwgIkNhIiwgIlRoYWwiKQpudW1iZXJpY192YXJpYWJsZXMgPC0gYygiQWdlIiwgIlJlc3RCUCIsICJDaG9sIiwgIk1heEhSIiwgIk9sZHBlYWsiKQpgYGAKClRoZSBiYXNpYyBzdGF0aXN0aWNzIG9mIGBkYXRhYCB3YXMgYmVsb3cgOgoKYGBge3J9IApzdW1tYXJ5KGRhdGEpCmBgYAoKQmVjYXVzZSB0aGUgYGRhdGFgIGNvbnRhaW4gbWlzc2luZyB2YWx1ZXMgYW5kIEkgd291bGQgYmUgcmVtb3ZlZCBiZWZvcmUgYnVpbGRpbmcgbW9kZWwKCmBgYHtyfQpkYXRhIDwtIG5hLm9taXQoZGF0YSkKYGBgCgpBZnRlciByZW1vdmluZyB0aGUgTkEsIHRoZSB0YWJsZSBjb25zaXN0ZWQgb2YgYHIgZGltKGhlYXJ0KVsxXWAgcm93cyBvZiBwYXRpZW50cy4KCkJlY2F1c2UgdGhlIGBBSERgIGlzIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgd2l0aCB0d28gdmFsdWVzIDogKipZZXMqKiAmICAqKk5vKiosIHNvIEknbSB1c2luZyBib3hwbG90IHRvIHNob3cgdGhlIGRpZmZpcmVuY2Ugb2YgYEFIRGAgd2l0aCBjb250aW51b3VzIHZhcmlhYmxlcy4KCmBgYHtyfQpwMSA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEsIGFlcyh4PUFIRCwgeT1BZ2UsIGNvbG9yPUFIRCkpICsgZ2VvbV9ib3hwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIApwMiA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEsIGFlcyh4PUFIRCwgeT1SZXN0QlAsIGNvbG9yPUFIRCkpICsgZ2VvbV9ib3hwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIApwMyA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEsIGFlcyh4PUFIRCwgeT1DaG9sLCBjb2xvcj1BSEQpKSArIGdlb21fYm94cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAKcDQgPC0gZ2dwbG90KGRhdGEgPSBkYXRhLCBhZXMoeD1BSEQsIHk9TWF4SFIsIGNvbG9yPUFIRCkpICsgZ2VvbV9ib3hwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIApwNSA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEsIGFlcyh4PUFIRCwgeT1PbGRwZWFrLCBjb2xvcj1BSEQpKSArIGdlb21fYm94cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAKZ3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBwNSkKYGBgCgpCYXNlIG9uIHRoZSBhYm92ZSBjaGFydHMsIHRoZSBib3hwbG90IHNob3cgdGhlIGRpZmZlcmVuY2UgaW4gZGlzdHJpYnV0aW9uIG9mIGBBSERgIGJ5IGBBZ2VgLCBgTWF4SFJgIGFuZCBgT2xkcGVha2AuIFRoZXNlIGh5cG90aGVzaXMgd291bGQgYmUgdGVzdGVkIGJ5IHQtdGVzdC4KCiMgSHlwb3RoZXNpcyB0ZXN0aW5nCgpgYGB7cn0KdC50ZXN0KEFnZSB+IEFIRCwgZGF0YT0gZGF0YSkKdC50ZXN0KE1heEhSIH4gQUhELCBkYXRhPSBkYXRhKQp0LnRlc3QoT2xkcGVhayB+IEFIRCwgZGF0YT0gZGF0YSkKYGBgCgpUaGUgYHQudGVzdGAgc2hvdyB0aGVyZSB3YXMgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIG1lYW4gb2YgYEFnZWAsIGBNYXhIUmAsIGBPbGRwZWFrYCBiZXR3ZWVuIGdyb3VwIG9mIHBhdGllbmQgd2hvIGhhZCBBSEQgb3Igbm8gQUhELgoKIyBCdWlsZCBuZXVyYWwgbmV0d29yayBtb2RlbCAKCiMjIFByZS1wcm9jZXNzIGRhdGEKClNvIGxldCdzIGJlZ2luIHRvIGJ1aWxkIGEgbmV1cmFsIG5ldHdvcmsgbW9kZWwuIEluIHRoaXMgcmVwb3J0LCBJIHdvdWxkIGJlIGJ1aWx0IGEgbW9kZWwgbmV1cmFsIG5ldHdvcmsgb2YgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIEFIRCB2YXJpYWJsZSBpbiBvcmRlciB0byBwcmVkaWN0IHBhdGllbnRzIHdpdGggaGVhcnQgZGlzZWFzZSBvciBub3Q/IChjb3JyZXNwb25kaW5nIHRvIHR3byB2YWx1ZXMgYHllc2Agb3IgYG5vYCkuCgpGaXJzdCwgSSB1c2VkIGBzYW1wbGUuc3BsaXQoKWAgZnVuY3Rpb24gdG8gc2VwYXJhdGUgYGRhdGFgIGludG8gdHdvIHBhcnRzIDogYHRyYWluaW5nIGRhdGFgIGFuZCBgdGVzdGluZyBkYXRhYC4KCmBgYHtyfQpzZXQuc2VlZCgxMDApCnNwbGl0IDwtIHNhbXBsZS5zcGxpdChkYXRhJEFIRCwgU3BsaXRSYXRpbyA9IDAuOCkKYGBgCgoKYGBge3J9CiMgQ3JlYXRlIHlfdHJhaW4gaW4gdHJhaW5pbmcgZGF0YQp5X3RyYWluIDwtIHN1YnNldChkYXRhLCBzcGxpdCA9PSBUUlVFKQp5X3RyYWluIDwtIHlfdHJhaW5bMTRdCnlfdHJhaW4gPC0gYXMubWF0cml4KHlfdHJhaW4pCnlfdHJhaW4gPC0gYXMudmVjdG9yKHlfdHJhaW4pCnlfdHJhaW4gPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoeV90cmFpbikpCgojIENyZWF0ZSB5X3Rlc3QgaW4gdGVzdGluZyBkYXRhCnlfdGVzdCA8LSBzdWJzZXQoZGF0YSwgc3BsaXQgPT0gRkFMU0UpCnlfdGVzdCA8LSB5X3Rlc3RbMTRdCnlfdGVzdCA8LSBhcy5tYXRyaXgoeV90ZXN0KQp5X3Rlc3QgPC0gYXMudmVjdG9yKHlfdGVzdCkKeV90ZXN0IDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHlfdGVzdCkpCmBgYAoKCkluIHRoaXMgYWJvdmUgbGluZXMgY29kZSwgSSBjcmVhdGVkIGB5X3RyYWluYCBhbmQgYHlfdGVzdGAgdmFyaWFibGVzLiBCb3RoIHdlcmUgY29udmVydGVkIHRvICoqbnVtZXJpYyoqIGRhdGEgdHlwZSBpbiBvcmRlciB0byBzdWl0YWJsZSB3aXRoIHJlcXVpcmUgb2YgbW9kZWwuIEl0IGNvbnRhaW5zIHRoZSBhY3R1YWwgcmVzdWx0IG9mIGBBSERgIHZhcmlhYmxlLiBgeV90cmFpbmAgYWNjb3VudHMgZm9yIDgwJSBvZiBgQUhEYCBhbmQgYHlfdGVzdGAgdGFrZXMgdXAgdGhlIHJlc3Qgb2YgdGhlIEFIRCB2YXJpYWJsZS4gVGhlbiwgSSBjcmVhdGVkIGB4X3RyYWluYCBhbmQgYHhfdGVzdGAgY29ycmVzcG9uZGluZyBgeV90cmFpbmAgYW5kIGB5X3Rlc3RgIGJ1dCBpbiB0aGlzIGB4X3RyYWluYCBhbmQgYHhfdGVzdGAsIGJvdGggY29udGFpbiByZW1haW5pbmcgMTMgY29sdW1ucyBleGNlcHQgYEFIRGAgY29sdW1uLiBUaGVyZSBhcmUgdGhlIG5lY2Vzc2FyeSB2YXJpYWJsZXMgdG8gcHJlZGljdCB0aGUgQUhEIHZhcmlhYmxlLgoKYGBge3J9CiMgQ3JlYXRlIHhfdHJhaW4gCnhfdHJhaW4gPC0gc3Vic2V0KGRhdGEsIHNwbGl0ID09IFRSVUUpCnhfdHJhaW4gPC0geF90cmFpblsxOjEzXQp4X3RyYWluJEFnZSA8LSBhcy5udW1lcmljKHhfdHJhaW4kQWdlKQp4X3RyYWluJFNleCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3RyYWluJFNleCkpCnhfdHJhaW4kQ2hlc3RQYWluIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHhfdHJhaW4kQ2hlc3RQYWluKSkKeF90cmFpbiRSZXN0QlAgPC0gYXMubnVtZXJpYyh4X3RyYWluJFJlc3RCUCkKeF90cmFpbiRDaG9sIDwtIGFzLm51bWVyaWMoeF90cmFpbiRDaG9sKQp4X3RyYWluJEZicyA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3RyYWluJEZicykpCnhfdHJhaW4kUmVzdEVDRyA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3RyYWluJFJlc3RFQ0cpKQp4X3RyYWluJE1heEhSIDwtIGFzLm51bWVyaWMoeF90cmFpbiRNYXhIUikKeF90cmFpbiRFeEFuZyA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3RyYWluJEV4QW5nKSkKeF90cmFpbiRPbGRwZWFrIDwtIGFzLm51bWVyaWMoeF90cmFpbiRPbGRwZWFrKQp4X3RyYWluJFNsb3BlIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHhfdHJhaW4kU2xvcGUpKQp4X3RyYWluJENhIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHhfdHJhaW4kQ2EpKQp4X3RyYWluJFRoYWw8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3RyYWluJFRoYWwpKQoKCnhfdHJhaW4gPC0gYXMubWF0cml4KHhfdHJhaW4pCnhfdHJhaW4gPC0gc2NhbGUoeF90cmFpbikKCgojIENyZWF0ZSB4X3Rlc3QKeF90ZXN0IDwtIHN1YnNldChkYXRhLCBzcGxpdCA9PSBGQUxTRSkKeF90ZXN0IDwtIHhfdGVzdFsxOjEzXQp4X3Rlc3QkQWdlIDwtIGFzLm51bWVyaWMoeF90ZXN0JEFnZSkKeF90ZXN0JFNleCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3Rlc3QkU2V4KSkKeF90ZXN0JENoZXN0UGFpbiA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3Rlc3QkQ2hlc3RQYWluKSkKeF90ZXN0JFJlc3RCUCA8LSBhcy5udW1lcmljKHhfdGVzdCRSZXN0QlApCnhfdGVzdCRDaG9sIDwtIGFzLm51bWVyaWMoeF90ZXN0JENob2wpCnhfdGVzdCRGYnMgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoeF90ZXN0JEZicykpCnhfdGVzdCRSZXN0RUNHIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHhfdGVzdCRSZXN0RUNHKSkKeF90ZXN0JE1heEhSIDwtIGFzLm51bWVyaWMoeF90ZXN0JE1heEhSKQp4X3Rlc3QkRXhBbmcgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoeF90ZXN0JEV4QW5nKSkKeF90ZXN0JE9sZHBlYWsgPC0gYXMubnVtZXJpYyh4X3Rlc3QkT2xkcGVhaykKeF90ZXN0JFNsb3BlIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHhfdGVzdCRTbG9wZSkpCnhfdGVzdCRDYSA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3Rlc3QkQ2EpKQp4X3Rlc3QkVGhhbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4X3Rlc3QkVGhhbCkpCgoKeF90ZXN0IDwtIGFzLm1hdHJpeCh4X3Rlc3QpCmNvbF9tZWFuc190cmFpbiA8LSBhdHRyKHhfdHJhaW4sICJzY2FsZWQ6Y2VudGVyIikgCmNvbF9zdGRkZXZzX3RyYWluIDwtIGF0dHIoeF90cmFpbiwgInNjYWxlZDpzY2FsZSIpCnhfdGVzdCA8LSBzY2FsZSh4X3Rlc3QsIGNlbnRlciA9IGNvbF9tZWFuc190cmFpbiwgc2NhbGUgPSBjb2xfc3RkZGV2c190cmFpbikKYGBgCgpUaGVuIEkgaGF2ZSBgeF90cmFpbmAgYW5kIGB4X3Rlc3RgLCB0aGVyZSB3YXMgc2NhbGVkIGJ5IGBzY2FsZSgpYCBmdW5jdGlvbi4gVGhlIGB4X3Rlc3RgIHdhcyBzY2FsZWQgd2l0aCAqKm1lYW4qKiBhbmQgKipzdGFuZGFyZCBkZXZpYXRpb24qKiBvZiBgeF90cmFpbmAuIFRoZSBwdXJwb3NlIG9mIHRoaXMgaXMgdG8gZml0IHRoZSBtb2RlbCBhbmQgbm90IGJlIG92ZXJmaXRpbmcuCgojIyBCdWlsZCBuZXVyYWwgbmV0d29yayB3aXRoIEFIRAoKQWZ0ZXIgcHJlLXByb2Nlc3NlZCBkYXRhIGFuZCBjcmVhdGVkICoqVHJhaW5nIGRhdGEqKiBhbmQgKipUZXN0aW5nIGRhdGEqKiwgSSBzdGFydGVkIHRvIGJ1aWxkIG1vZGVsIGFuZCB0cmFpbmluZyBtb2RlbC4KCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkKbW9kZWwgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgYWN0aXZhdGlvbiA9ICdyZWx1JyxpbnB1dF9zaGFwZSA9IGRpbSh4X3RyYWluKVsyXSkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxLCBhY3RpdmF0aW9uID0gJ3NpZ21vaWQnKQoKc3VtbWFyeShtb2RlbCkKCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IG9wdGltaXplcl9hZGFtKCksCiAgbG9zcyA9ICdiaW5hcnlfY3Jvc3NlbnRyb3B5JywKICBtZXRyaWNzID0gYygnYWNjdXJhY3knKQopCmBgYAoKSW4gdGhpcyBhYm92ZSBsaW5lcyBjb2RlLCBJIGJ1aWx0IGEgbW9kZWwgbmV1cmFsIG5ldHdvcmsuIFRoaXMgbW9kZWwgaGFzIHR3byBsYXllcnMgOiBJbnB1dCBhbmQgT3V0cHV0IGFuZCB1c2VkIGFjdGl2YXRpb24gZnVuY3Rpb25zIHdlcmUgKipyZWx1KiogYW5kICoqc2lnbW9pZCoqLiBJbiBjb21waWxlIG1vZGVsLCB0aGUgbG9zcyBmdW5jdGlvbiBpcyAqKmJpbmFyeV9jcm9zc2VudHJvcHkqKiBtZXRob2QsIHRoZSBvcHRpbWl6YXRpb24gbWV0aG9kIGlzICoqYWRhbSoqIGFuZCB1c2VkICoqYWNjdXJhbmN5KiogdG8gZXZhbHV0ZSB0aGUgYWNjdXJhbmN5IG1vZGVsLgoKIyMgVHJhaW5pbmcgbW9kZWwKCkxldCdzIHRyYWluaW5nIG1vZGVsIHdpdGggYGZpdCgpYCBhbmQgdGhlIG51bWJlciBvZiAqKmVwb2NoKiogYXJlIDIwMC4KCmBgYHtyfQptb2RlbCAlPiUgZml0KHhfdHJhaW4sIHlfdHJhaW4sIGVwb2NoID0gMjAwKQpgYGAKCiMjIEV2YWx1YXRlIGFjY3VyYWN5CgpgYGB7cn0Kc2NvcmUgPC0gbW9kZWwgJT4lIGV2YWx1YXRlKHhfdGVzdCwgeV90ZXN0KQpgYGAKCkFmdGVyIHRyYWluaW5nLCBJIGV2YWx1YXRlZCB0aGUgbG9zcyBhbmQgYWNjdXJhbmN5IGluIHRlc3RpbmcgZGF0YS4gVGhlIHJlc3VsdCBhcmUgKipsb3NzKiogOiAwLjMwNzcgYW5kICoqYWNjdXJhbmN5KiogOiAwLjgzMDUuIFdhcyB0aGlzIHJlc3VsdCBPSyA/CgojIyBNYWtlIHByZWRpY3Rpb25zCgpUaGUgb3V0cHV0IG9mIHRoZSBtb2RlbCBpcyBzdG9yZWQgaW4gdGhlIGBwMWAgdmFyaWFibGUKCmBgYHtyfQpwMSA8LSBtb2RlbCAlPiUgcHJlZGljdCh4X3Rlc3QpCmhlYWQocDEgLCAyMCkKYGBgCgpGaW5hbGx5LCBJIHVzZWQgKipjb25mdXNpb24gbWF0cml4KiogbWV0aG9kIHRvIGNoZWNrIHRoZSByZXN1bHQgYWdhaW4gOgoKYGBge3J9CnByZWQgPSBhcy5mYWN0b3IoaWZlbHNlKHAxID4gMC41ICwgJzEnLCcwJykpCgoKY29uZnVzaW9uTWF0cml4KHByZWQsZmFjdG9yKHlfdGVzdCkpCgpgYGAKCiMgQ29uY2x1c2lvbgoKSW4gdGhpcyByZXBvcnQsIEkgaGFkIGJ1aWx0IGEgbmV1cmFsIG5ldHdvcmsgbW9kZWwgd2l0aCBsb2dpc3RpYyByZWdyZXNzaW9uLiBCeSB1c2luZyB0aGUgKiphZGFtKiogbWV0aG9kIHRvIG9wdGltaXplIHRoZSBsb3NzIGZ1bmN0aW9uIGFuZCB1c2luZyB0d28gYWN0aXZhdGlvbiBmdW5jdGlvbnMgOiAqKnNpZ21vaWQqKiBhbmQgKipyZWx1KiogdG8gYnVpbGQgdGhlIG1vZGVsLCB0aGUgcmVzdWx0IGlzIHF1aXRlIGdvb2Qgd2l0aCAqKmxvc3MqKiA6IDAuMzA3NyBhbmQgKiphY2N1cmFuY3kqKiA6IDAuODMwNS4gSG93ZXZlciwgdGhpcyBtb2RlbCB3YXMgbm90IHRoZSBtb3N0IG9wdGltYWwgbW9kZWwgYnV0IGl0IHdhcyB2ZXJ5IHVzZWZ1bCB0byBkaWFnbm9zZSB3aGF0IGEgcGF0aWVudCBoYXMgaGVhcnQgZGlzZWFzZSBvciBub3QgLSBhIGdvb2QgYXNzaXN0YW50IGZvciBhIGRvY3Rvci4KCg==