First load in the data:

test = read.csv("yelp_test.csv", as.is=TRUE)
train = read.csv("yelp_train.csv", as.is = TRUE)

Lets take a look at the data:

summary(head(train))
     funny           useful       user_cool         characters     user_average_stars
 Min.   :0.000   Min.   :0.00   Min.   :   0.00   Min.   : 313.0   Min.   :2.400     
 1st Qu.:0.000   1st Qu.:0.25   1st Qu.:   0.75   1st Qu.: 403.5   1st Qu.:3.143     
 Median :0.000   Median :1.50   Median :  27.00   Median : 761.5   Median :3.640     
 Mean   :1.167   Mean   :2.00   Mean   : 208.17   Mean   :1138.0   Mean   :3.530     
 3rd Qu.:0.000   3rd Qu.:2.75   3rd Qu.: 103.50   3rd Qu.:1257.5   3rd Qu.:4.055     
 Max.   :7.000   Max.   :6.00   Max.   :1074.00   Max.   :3285.0   Max.   :4.330     
 biz_review_count  user_useful       user_funny        biz_open          stars     
 Min.   : 6.00    Min.   :   1.0   Min.   :   0.0   Min.   :0.0000   Min.   :1.00  
 1st Qu.: 6.75    1st Qu.:   5.5   1st Qu.:   0.0   1st Qu.:1.0000   1st Qu.:2.00  
 Median :40.50    Median :  78.5   Median :  13.5   Median :1.0000   Median :2.00  
 Mean   :41.50    Mean   : 282.3   Mean   : 198.2   Mean   :0.8333   Mean   :2.50  
 3rd Qu.:75.75    3rd Qu.: 211.5   3rd Qu.:  49.5   3rd Qu.:1.0000   3rd Qu.:2.75  
 Max.   :79.00    Max.   :1299.0   Max.   :1105.0   Max.   :1.0000   Max.   :5.00  
     date             biz_stars         words        user_review_count      cool       
 Length:6           Min.   :3.000   Min.   : 60.00   Min.   :  5.00    Min.   :0.0000  
 Class :character   1st Qu.:3.125   1st Qu.: 69.75   1st Qu.:  5.25    1st Qu.:0.0000  
 Mode  :character   Median :3.750   Median :145.50   Median : 61.50    Median :0.0000  
                    Mean   :3.583   Mean   :215.83   Mean   :115.83    Mean   :0.8333  
                    3rd Qu.:4.000   3rd Qu.:252.00   3rd Qu.:188.25    3rd Qu.:0.0000  
                    Max.   :4.000   Max.   :612.00   Max.   :350.00    Max.   :5.0000  

** Create a column called positive which is equal to 1 if the review is positive and 0 otherwise. **

The review is positive when it received either a 4 or 5 start review:

test$positive <- ifelse(test$stars <4,0,1)
train$positive <- ifelse(train$stars <4,0,1)

** Make any other transformations to the dataset you determine to be helpful or necessary. **

Lets remove the date column since it will not be relevant:

test$date =NULL
train$date = NULL

We will also remove the stars variable. It would be unfait to use it, because it alone would we able to predict with accuray 100%, because positive is directly created from it.

test$stars =NULL
train$stars = NULL

Logistical Regression

lr <- glm(positive ~ ., data=train, family = binomial())

Without any transformations the error rate is:

pred_lr = predict(lr, newdata = test, type = "response")
pred_lr_05 <- ifelse(pred_lr < 0.5,0,1)
mean(pred_lr_05  != test$positive)
[1] 0.314

With any transformations, the algorithm does not do well, getting 31% wrong.

** show all steps in model selection as well as the tuning of any parameters. **

One possibility is to use the weights parameter, that way if either postive or negative reviews are more common, it will take that into account.

pos = sum(train$positive == 1)
neg = sum(train$positive == 0)
pos
[1] 1518
neg
[1] 1482
pos_test = sum(test$positive == 1)
neg_test = sum(test$positive == 0)
pos_test
[1] 482
neg_test
[1] 518

Both the training and the test data are pretty well balanced in half. So the weight parameter of glm will not be necessary.

Lets try to choose a different cutting point rather than 0.5

pred_lr_06 <- ifelse(pred_lr < 0.6,0,1)
mean(pred_lr_06  != test$positive)
[1] 0.293
pred_lr_04 <- ifelse(pred_lr < 0.4,0,1)
mean(pred_lr_04  != test$positive)
[1] 0.349

Choosing different cutting points made it a bit better. Lets explore a bit more:

pred_lr_055 <- ifelse(pred_lr < 0.55,0,1)
mean(pred_lr_055  != test$positive)
[1] 0.299
pred_lr_06 <- ifelse(pred_lr < 0.6,0,1)
mean(pred_lr_06  != test$positive)
[1] 0.293
pred_lr_065 <- ifelse(pred_lr < 0.65,0,1)
mean(pred_lr_065  != test$positive)
[1] 0.301
pred_lr_07 <- ifelse(pred_lr < 0.7,0,1)
mean(pred_lr_07  != test$positive)
[1] 0.327

The best cut is at 0.6, where the error rate is 29.3%

** interpretation of the coefficients **

summary(lr)

Call:
glm(formula = positive ~ ., family = binomial(), data = train)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.1820  -0.9399   0.2903   0.8938   3.9722  

Coefficients:
                     Estimate Std. Error z value Pr(>|z|)    
(Intercept)        -6.8505708  0.3804446 -18.007  < 2e-16 ***
funny              -0.3727667  0.0582204  -6.403 1.53e-10 ***
useful             -0.2458292  0.0434809  -5.654 1.57e-08 ***
user_cool           0.0001540  0.0006746   0.228   0.8194    
characters          0.0043128  0.0010592   4.072 4.67e-05 ***
user_average_stars  0.3428955  0.0415749   8.248  < 2e-16 ***
biz_review_count   -0.0003048  0.0003183  -0.958   0.3382    
user_useful        -0.0003098  0.0005125  -0.604   0.5456    
user_funny          0.0002966  0.0002457   1.207   0.2273    
biz_open            0.2280944  0.1470682   1.551   0.1209    
biz_stars           1.6389402  0.0881532  18.592  < 2e-16 ***
words              -0.0262599  0.0057694  -4.552 5.32e-06 ***
user_review_count  -0.0008507  0.0003937  -2.161   0.0307 *  
cool                0.6630444  0.0634887  10.443  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 4158.5  on 2999  degrees of freedom
Residual deviance: 3269.5  on 2986  degrees of freedom
AIC: 3297.5

Number of Fisher Scoring iterations: 5

The most important coefficients are funny, useful, characters, user_average_stars, biz_stars, words and cool. But the most important one is user_review_count. The others are not significant.

It makes sense that user_review count makes a difference, usually people will comment more on things that they like.

** Summarize your findings **

Logistic Regression does not seem like a good option for this data set. The minimal error found was around 29%. And in general the predictors don’t seem very significant.

SVM - Linear

#install.packages("e1071")
library(e1071)
package <U+393C><U+3E31>e1071<U+393C><U+3E32> was built under R version 3.3.3
svm_lin <- svm(positive ~ ., data=train, kernel="linear")

Without doing any tuning lest see the test error:

pred_lin = predict(svm_lin,newdata = test)
pred_lin_05 <- ifelse(pred_lin < 0.5,0,1)
mean(pred_lin_05  != test$positive)
[1] 0.339

The test error is even higher than linear regression, with an error of around 34%.

** show all steps in model selection as well as the tuning of any parameters. **

Lets do some tuning to get a better model. To start with lets tune the cost variable:

set.seed(222)
svm_lin_tune <- tune(svm, positive ~ ., data=train, kernel="linear", ranges=list(cost = c(0.1,1,2,5)))
svm_lin_best <- svm_lin_tune$best.model
pred_lin = predict(svm_lin_best,newdata = test)
pred_lin_05 <- ifelse(pred_lin < 0.5,0,1)
mean(pred_lin_05  != test$positive)
[1] 0.339
summary(svm_lin_best)

Call:
best.tune(method = svm, train.x = positive ~ ., data = train, ranges = list(cost = c(0.1, 
    1, 2, 5)), kernel = "linear")


Parameters:
   SVM-Type:  eps-regression 
 SVM-Kernel:  linear 
       cost:  1 
      gamma:  0.07692308 
    epsilon:  0.1 


Number of Support Vectors:  2787

The error did not dicrease and the error is still the same.

** interpretation of the coefficients **

The algorithm created 2787 supoort vectors. The best cost found is still 1.

** Summarize your findings **

Linear SVM did not perform well. The best found test error was 34%. Maybe polynomials or radial kernels will perform better.

SVM - Polynomial

#install.packages("e1071")
library(e1071)
package <U+393C><U+3E31>e1071<U+393C><U+3E32> was built under R version 3.3.3
svm_pol <- svm(positive ~ ., data=train, kernel="polynomial")

Without doing any tuning lest see the test error:

pred_pol = predict(svm_pol,newdata = test)
pred_pol_05 <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol_05  != test$positive)
[1] 0.431

The test error is even higher than linear regression, with an error of around 43%.

** show all steps in model selection as well as the tuning of any parameters. **

Lets do some tuning to get a better model. Lets tune the cost and gamma variables:

set.seed(222)
svm_pol1 <- svm(positive ~ ., data=train, kernel="polynomial", cost = 0.1)
svm_pol2 <- svm(positive ~ ., data=train, kernel="polynomial", cost = 0.5)
svm_pol3 <- svm(positive ~ ., data=train, kernel="polynomial", cost = 1)
svm_pol4 <- svm(positive ~ ., data=train, kernel="polynomial", cost = 5)
#svm_pol_tune <- tune(svm, positive ~ ., data=train, kernel="polynomial", ranges=list(cost = c(0.1,1,2,5), gamma = c(0.5,1,2,3)))
#svm_pol_best <- svm_pol_tune$best.model
pred_pol = predict(svm_pol1,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.343
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.353
pred_pol = predict(svm_pol3,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.345
pred_pol = predict(svm_pol4,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.324

Changing the cost did not change the results much. Lets try varying gamma, using the best cost found at 5.

set.seed(222)
svm_pol1 <- svm(positive ~ ., data=train, kernel="polynomial", degree = 1, cost = 5)
svm_pol2 <- svm(positive ~ ., data=train, kernel="polynomial", degree = 2, cost = 5)
svm_pol3 <- svm(positive ~ ., data=train, kernel="polynomial", degree = 3, cost = 5)
svm_pol4 <- svm(positive ~ ., data=train, kernel="polynomial", degree = 4, cost =5)
#svm_pol_tune <- tune(svm, positive ~ ., data=train, kernel="polynomial", ranges=list(cost = c(0.1,1,2,5), gamma = c(0.5,1,2,3)))
#svm_pol_best <- svm_pol_tune$best.model
pred_pol = predict(svm_pol1,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.367
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.366
pred_pol = predict(svm_pol3,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.324
pred_pol = predict(svm_pol4,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.344

** Summarize your findings **

best_model_svm =  svm(positive ~ ., data=train, kernel="polynomial", degree = 3, cost = 5)
summary(best_model_svm)

Call:
svm(formula = positive ~ ., data = train, kernel = "polynomial", degree = 3, cost = 5)


Parameters:
   SVM-Type:  eps-regression 
 SVM-Kernel:  polynomial 
       cost:  5 
     degree:  3 
      gamma:  0.07142857 
     coef.0:  0 
    epsilon:  0.1 


Number of Support Vectors:  2249
pred_pol = predict(best_model_svm,newdata = test)
pred_pol <- ifelse(pred_pol < 0.25,0,1)
mean(pred_pol  != test$positive)
[1] 0.264

The best model found has cost 5, cuts at 0.25, and has degree 3. The test error for it is 26%.

SVM - Radial

#install.packages("e1071")
library(e1071)
svm_rad <- svm(positive ~ ., data=train, kernel="radial")

Without doing any tuning lest see the test error for a radial svm:

pred_rad = predict(svm_rad,newdata = test)
pred_rad_05 <- ifelse(pred_rad < 0.5,0,1)
mean(pred_rad_05  != test$positive)
[1] 0.249

The test error for radial kernel seems more promising. Without any tuning it is so far the best test error. Even though it is still quite high at 25%

** show all steps in model selection as well as the tuning of any parameters. **

Lets do some tuning to get a better model. Lets tune the cost and gamma variables:

set.seed(222)
svm_pol1 <- svm(positive ~ ., data=train, kernel="radial", cost = 0.1)
svm_pol2 <- svm(positive ~ ., data=train, kernel="radial", cost = 0.5)
svm_pol3 <- svm(positive ~ ., data=train, kernel="radial", cost = 1)
svm_pol4 <- svm(positive ~ ., data=train, kernel="radial", cost = 5)
pred_pol = predict(svm_pol1,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.267
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.246
pred_pol = predict(svm_pol3,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.249
pred_pol = predict(svm_pol4,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.256

Changing the cost did not change the results much. Lets try varying gamma, using the best cost found at 0.5.

set.seed(222)
svm_pol1 <- svm(positive ~ ., data=train, kernel="radial", cost = 0.5, gamma = 0.1)
svm_pol2 <- svm(positive ~ ., data=train, kernel="radial", cost = 0.5, gamma = 0.2)
svm_pol3 <- svm(positive ~ ., data=train, kernel="radial", cost = 0.5, gamma = 0.3)
svm_pol4 <- svm(positive ~ ., data=train, kernel="radial", cost = 0.5, gamma = 0.5)
pred_pol = predict(svm_pol1,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.25
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.258
pred_pol = predict(svm_pol3,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.261
pred_pol = predict(svm_pol4,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.275

The default gamma seems like the best option.

Lets try varying where we cut:

svm_pol2 <- svm(positive ~ ., data=train, kernel="radial", cost = 0.5)
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.25,0,1)
mean(pred_pol  != test$positive)
[1] 0.284
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.3,0,1)
mean(pred_pol  != test$positive)
[1] 0.271
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.35,0,1)
mean(pred_pol  != test$positive)
[1] 0.257
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.4,0,1)
mean(pred_pol  != test$positive)
[1] 0.248
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.45,0,1)
mean(pred_pol  != test$positive)
[1] 0.247
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.50,0,1)
mean(pred_pol  != test$positive)
[1] 0.246
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.55,0,1)
mean(pred_pol  != test$positive)
[1] 0.25
pred_pol = predict(svm_pol2,newdata = test)
pred_pol <- ifelse(pred_pol < 0.60,0,1)
mean(pred_pol  != test$positive)
[1] 0.26

The best cut found is still at 0.5.

** Summarize your findings **

best_model_svm =  svm(positive ~ ., data=train, kernel="radial", cost = 0.5)
summary(best_model_svm)

Call:
svm(formula = positive ~ ., data = train, kernel = "radial", cost = 0.5)


Parameters:
   SVM-Type:  eps-regression 
 SVM-Kernel:  radial 
       cost:  0.5 
      gamma:  0.07142857 
    epsilon:  0.1 


Number of Support Vectors:  2190
pred_pol = predict(best_model_svm,newdata = test)
pred_pol <- ifelse(pred_pol < 0.5,0,1)
mean(pred_pol  != test$positive)
[1] 0.246

The best model found has cost 0.5, cuts at 0.5, and has the default gamma of 0.07. It created 2190 support vectors. The best test error for it is 24,6%.

** Compare the linear, polynomial, and radial kernels. **

Out of all the models, the radial kernel worked the best.

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpGaXJzdCBsb2FkIGluIHRoZSBkYXRhOg0KDQpgYGB7cn0NCnRlc3QgPSByZWFkLmNzdigieWVscF90ZXN0LmNzdiIsIGFzLmlzPVRSVUUpDQp0cmFpbiA9IHJlYWQuY3N2KCJ5ZWxwX3RyYWluLmNzdiIsIGFzLmlzID0gVFJVRSkNCmBgYA0KDQpMZXRzIHRha2UgYSBsb29rIGF0IHRoZSBkYXRhOg0KDQpgYGB7cn0NCnN1bW1hcnkoaGVhZCh0cmFpbikpDQpgYGANCg0KDQoqKiBDcmVhdGUgYSBjb2x1bW4gY2FsbGVkIHBvc2l0aXZlIHdoaWNoIGlzIGVxdWFsIHRvIDEgaWYgdGhlIHJldmlldyBpcyBwb3NpdGl2ZSBhbmQgMCBvdGhlcndpc2UuICoqDQoNClRoZSByZXZpZXcgaXMgcG9zaXRpdmUgd2hlbiBpdCByZWNlaXZlZCBlaXRoZXIgYSA0IG9yIDUgc3RhcnQgcmV2aWV3Og0KDQpgYGB7cn0NCnRlc3QkcG9zaXRpdmUgPC0gaWZlbHNlKHRlc3Qkc3RhcnMgPDQsMCwxKQ0KdHJhaW4kcG9zaXRpdmUgPC0gaWZlbHNlKHRyYWluJHN0YXJzIDw0LDAsMSkNCmBgYA0KDQoqKiBNYWtlIGFueSBvdGhlciB0cmFuc2Zvcm1hdGlvbnMgdG8gdGhlIGRhdGFzZXQgeW91IGRldGVybWluZSB0byBiZSBoZWxwZnVsIG9yIG5lY2Vzc2FyeS4gKioNCg0KTGV0cyByZW1vdmUgdGhlIGRhdGUgY29sdW1uIHNpbmNlIGl0IHdpbGwgbm90IGJlIHJlbGV2YW50Og0KYGBge3J9DQp0ZXN0JGRhdGUgPU5VTEwNCnRyYWluJGRhdGUgPSBOVUxMDQpgYGANCg0KV2Ugd2lsbCBhbHNvIHJlbW92ZSB0aGUgc3RhcnMgdmFyaWFibGUuIEl0IHdvdWxkIGJlIHVuZmFpdCB0byB1c2UgaXQsIGJlY2F1c2UgaXQgYWxvbmUgd291bGQgd2UgYWJsZSB0byBwcmVkaWN0IHdpdGggYWNjdXJheSAxMDAlLCBiZWNhdXNlIHBvc2l0aXZlIGlzIGRpcmVjdGx5IGNyZWF0ZWQgZnJvbSBpdC4NCmBgYHtyfQ0KdGVzdCRzdGFycyA9TlVMTA0KdHJhaW4kc3RhcnMgPSBOVUxMDQpgYGANCg0KI0xvZ2lzdGljYWwgUmVncmVzc2lvbg0KDQpgYGB7cn0NCmxyIDwtIGdsbShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGZhbWlseSA9IGJpbm9taWFsKCkpDQpgYGANCg0KV2l0aG91dCBhbnkgdHJhbnNmb3JtYXRpb25zIHRoZSBlcnJvciByYXRlIGlzOg0KDQpgYGB7cn0NCnByZWRfbHIgPSBwcmVkaWN0KGxyLCBuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQpwcmVkX2xyXzA1IDwtIGlmZWxzZShwcmVkX2xyIDwgMC41LDAsMSkNCm1lYW4ocHJlZF9scl8wNSAgIT0gdGVzdCRwb3NpdGl2ZSkNCg0KYGBgDQoNCldpdGggYW55IHRyYW5zZm9ybWF0aW9ucywgdGhlIGFsZ29yaXRobSBkb2VzIG5vdCBkbyB3ZWxsLCBnZXR0aW5nIDMxJSB3cm9uZy4NCg0KKiogc2hvdyBhbGwgc3RlcHMgaW4gbW9kZWwgc2VsZWN0aW9uIGFzIHdlbGwgYXMgdGhlIHR1bmluZyBvZiBhbnkgcGFyYW1ldGVycy4gKioNCg0KT25lIHBvc3NpYmlsaXR5IGlzIHRvIHVzZSB0aGUgd2VpZ2h0cyBwYXJhbWV0ZXIsIHRoYXQgd2F5IGlmIGVpdGhlciBwb3N0aXZlIG9yIG5lZ2F0aXZlIHJldmlld3MgYXJlIG1vcmUgY29tbW9uLCBpdCB3aWxsIHRha2UgdGhhdCBpbnRvIGFjY291bnQuDQoNCmBgYHtyfQ0KcG9zID0gc3VtKHRyYWluJHBvc2l0aXZlID09IDEpDQpuZWcgPSBzdW0odHJhaW4kcG9zaXRpdmUgPT0gMCkNCnBvcw0KbmVnDQpwb3NfdGVzdCA9IHN1bSh0ZXN0JHBvc2l0aXZlID09IDEpDQpuZWdfdGVzdCA9IHN1bSh0ZXN0JHBvc2l0aXZlID09IDApDQpwb3NfdGVzdA0KbmVnX3Rlc3QNCmBgYA0KDQpCb3RoIHRoZSB0cmFpbmluZyBhbmQgdGhlIHRlc3QgZGF0YSBhcmUgcHJldHR5IHdlbGwgYmFsYW5jZWQgaW4gaGFsZi4gU28gdGhlIHdlaWdodCBwYXJhbWV0ZXIgb2YgZ2xtIHdpbGwgbm90IGJlIG5lY2Vzc2FyeS4NCg0KTGV0cyB0cnkgdG8gY2hvb3NlIGEgZGlmZmVyZW50IGN1dHRpbmcgcG9pbnQgcmF0aGVyIHRoYW4gMC41DQpgYGB7cn0NCnByZWRfbHJfMDYgPC0gaWZlbHNlKHByZWRfbHIgPCAwLjYsMCwxKQ0KbWVhbihwcmVkX2xyXzA2ICAhPSB0ZXN0JHBvc2l0aXZlKQ0KcHJlZF9scl8wNCA8LSBpZmVsc2UocHJlZF9sciA8IDAuNCwwLDEpDQptZWFuKHByZWRfbHJfMDQgICE9IHRlc3QkcG9zaXRpdmUpDQpgYGANCg0KQ2hvb3NpbmcgZGlmZmVyZW50IGN1dHRpbmcgcG9pbnRzIG1hZGUgaXQgYSBiaXQgYmV0dGVyLiBMZXRzIGV4cGxvcmUgYSBiaXQgbW9yZToNCg0KYGBge3J9DQpwcmVkX2xyXzA1NSA8LSBpZmVsc2UocHJlZF9sciA8IDAuNTUsMCwxKQ0KbWVhbihwcmVkX2xyXzA1NSAgIT0gdGVzdCRwb3NpdGl2ZSkNCnByZWRfbHJfMDYgPC0gaWZlbHNlKHByZWRfbHIgPCAwLjYsMCwxKQ0KbWVhbihwcmVkX2xyXzA2ICAhPSB0ZXN0JHBvc2l0aXZlKQ0KcHJlZF9scl8wNjUgPC0gaWZlbHNlKHByZWRfbHIgPCAwLjY1LDAsMSkNCm1lYW4ocHJlZF9scl8wNjUgICE9IHRlc3QkcG9zaXRpdmUpDQpwcmVkX2xyXzA3IDwtIGlmZWxzZShwcmVkX2xyIDwgMC43LDAsMSkNCm1lYW4ocHJlZF9scl8wNyAgIT0gdGVzdCRwb3NpdGl2ZSkNCmBgYA0KDQpUaGUgYmVzdCBjdXQgaXMgYXQgMC42LCB3aGVyZSB0aGUgZXJyb3IgcmF0ZSBpcyAyOS4zJQ0KDQoqKiBpbnRlcnByZXRhdGlvbiBvZiB0aGUgY29lZmZpY2llbnRzICoqDQoNCmBgYHtyfQ0Kc3VtbWFyeShscikNCmBgYA0KDQpUaGUgbW9zdCBpbXBvcnRhbnQgY29lZmZpY2llbnRzIGFyZSBmdW5ueSwgdXNlZnVsLCBjaGFyYWN0ZXJzLCB1c2VyX2F2ZXJhZ2Vfc3RhcnMsIGJpel9zdGFycywgd29yZHMgYW5kIGNvb2wuIEJ1dCB0aGUgbW9zdCBpbXBvcnRhbnQgb25lIGlzIHVzZXJfcmV2aWV3X2NvdW50LiBUaGUgb3RoZXJzIGFyZSBub3Qgc2lnbmlmaWNhbnQuDQoNCkl0IG1ha2VzIHNlbnNlIHRoYXQgdXNlcl9yZXZpZXcgY291bnQgbWFrZXMgYSBkaWZmZXJlbmNlLCB1c3VhbGx5IHBlb3BsZSB3aWxsIGNvbW1lbnQgbW9yZSBvbiB0aGluZ3MgdGhhdCB0aGV5IGxpa2UuDQoNCg0KKiogU3VtbWFyaXplIHlvdXIgZmluZGluZ3MgKioNCg0KTG9naXN0aWMgUmVncmVzc2lvbiBkb2VzIG5vdCBzZWVtIGxpa2UgYSBnb29kIG9wdGlvbiBmb3IgdGhpcyBkYXRhIHNldC4gVGhlIG1pbmltYWwgZXJyb3IgZm91bmQgd2FzIGFyb3VuZCAyOSUuIEFuZCBpbiBnZW5lcmFsIHRoZSBwcmVkaWN0b3JzIGRvbid0IHNlZW0gdmVyeSBzaWduaWZpY2FudC4NCg0KIyBTVk0gLSBMaW5lYXINCg0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQ0KbGlicmFyeShlMTA3MSkNCnN2bV9saW4gPC0gc3ZtKHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJsaW5lYXIiKQ0KYGBgDQoNCldpdGhvdXQgZG9pbmcgYW55IHR1bmluZyBsZXN0IHNlZSB0aGUgdGVzdCBlcnJvcjoNCg0KYGBge3J9DQpwcmVkX2xpbiA9IHByZWRpY3Qoc3ZtX2xpbixuZXdkYXRhID0gdGVzdCkNCnByZWRfbGluXzA1IDwtIGlmZWxzZShwcmVkX2xpbiA8IDAuNSwwLDEpDQptZWFuKHByZWRfbGluXzA1ICAhPSB0ZXN0JHBvc2l0aXZlKQ0KYGBgDQoNClRoZSB0ZXN0IGVycm9yIGlzIGV2ZW4gaGlnaGVyIHRoYW4gbGluZWFyIHJlZ3Jlc3Npb24sIHdpdGggYW4gZXJyb3Igb2YgYXJvdW5kIDM0JS4NCg0KKiogc2hvdyBhbGwgc3RlcHMgaW4gbW9kZWwgc2VsZWN0aW9uIGFzIHdlbGwgYXMgdGhlIHR1bmluZyBvZiBhbnkgcGFyYW1ldGVycy4gKioNCg0KTGV0cyBkbyBzb21lIHR1bmluZyB0byBnZXQgYSBiZXR0ZXIgbW9kZWwuIFRvIHN0YXJ0IHdpdGggbGV0cyB0dW5lIHRoZSBjb3N0IHZhcmlhYmxlOg0KDQpgYGB7cn0NCnNldC5zZWVkKDIyMikNCnN2bV9saW5fdHVuZSA8LSB0dW5lKHN2bSwgcG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9ImxpbmVhciIsIHJhbmdlcz1saXN0KGNvc3QgPSBjKDAuMSwxLDIsNSkpKQ0Kc3ZtX2xpbl9iZXN0IDwtIHN2bV9saW5fdHVuZSRiZXN0Lm1vZGVsDQoNCnByZWRfbGluID0gcHJlZGljdChzdm1fbGluX2Jlc3QsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX2xpbl8wNSA8LSBpZmVsc2UocHJlZF9saW4gPCAwLjUsMCwxKQ0KbWVhbihwcmVkX2xpbl8wNSAgIT0gdGVzdCRwb3NpdGl2ZSkNCmBgYA0KYGBge3J9DQpzdW1tYXJ5KHN2bV9saW5fYmVzdCkNCmBgYA0KDQpUaGUgZXJyb3IgZGlkIG5vdCBkaWNyZWFzZSBhbmQgdGhlIGVycm9yIGlzIHN0aWxsIHRoZSBzYW1lLg0KDQoqKiBpbnRlcnByZXRhdGlvbiBvZiB0aGUgY29lZmZpY2llbnRzICoqDQoNClRoZSBhbGdvcml0aG0gY3JlYXRlZCAyNzg3IHN1cG9vcnQgdmVjdG9ycy4gVGhlIGJlc3QgY29zdCBmb3VuZCBpcyBzdGlsbCAxLg0KDQoqKiBTdW1tYXJpemUgeW91ciBmaW5kaW5ncyAqKg0KDQpMaW5lYXIgU1ZNIGRpZCBub3QgcGVyZm9ybSB3ZWxsLiBUaGUgYmVzdCBmb3VuZCB0ZXN0IGVycm9yIHdhcyAzNCUuIE1heWJlIHBvbHlub21pYWxzIG9yIHJhZGlhbCBrZXJuZWxzIHdpbGwgcGVyZm9ybSBiZXR0ZXIuDQoNCiMgU1ZNIC0gUG9seW5vbWlhbA0KDQoNCmBgYHtyfQ0KI2luc3RhbGwucGFja2FnZXMoImUxMDcxIikNCmxpYnJhcnkoZTEwNzEpDQpzdm1fcG9sIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icG9seW5vbWlhbCIpDQpgYGANCg0KV2l0aG91dCBkb2luZyBhbnkgdHVuaW5nIGxlc3Qgc2VlIHRoZSB0ZXN0IGVycm9yOg0KDQpgYGB7cn0NCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sLG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2xfMDUgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC41LDAsMSkNCm1lYW4ocHJlZF9wb2xfMDUgICE9IHRlc3QkcG9zaXRpdmUpDQpgYGANCg0KVGhlIHRlc3QgZXJyb3IgaXMgZXZlbiBoaWdoZXIgdGhhbiBsaW5lYXIgcmVncmVzc2lvbiwgd2l0aCBhbiBlcnJvciBvZiBhcm91bmQgNDMlLg0KDQoqKiBzaG93IGFsbCBzdGVwcyBpbiBtb2RlbCBzZWxlY3Rpb24gYXMgd2VsbCBhcyB0aGUgdHVuaW5nIG9mIGFueSBwYXJhbWV0ZXJzLiAqKg0KDQpMZXRzIGRvIHNvbWUgdHVuaW5nIHRvIGdldCBhIGJldHRlciBtb2RlbC4gTGV0cyB0dW5lIHRoZSBjb3N0IGFuZCBnYW1tYSB2YXJpYWJsZXM6DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjIyKQ0KDQpzdm1fcG9sMSA8LSBzdm0ocG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9InBvbHlub21pYWwiLCBjb3N0ID0gMC4xKQ0Kc3ZtX3BvbDIgPC0gc3ZtKHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJwb2x5bm9taWFsIiwgY29zdCA9IDAuNSkNCnN2bV9wb2wzIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icG9seW5vbWlhbCIsIGNvc3QgPSAxKQ0Kc3ZtX3BvbDQgPC0gc3ZtKHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJwb2x5bm9taWFsIiwgY29zdCA9IDUpDQoNCiNzdm1fcG9sX3R1bmUgPC0gdHVuZShzdm0sIHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJwb2x5bm9taWFsIiwgcmFuZ2VzPWxpc3QoY29zdCA9IGMoMC4xLDEsMiw1KSwgZ2FtbWEgPSBjKDAuNSwxLDIsMykpKQ0KI3N2bV9wb2xfYmVzdCA8LSBzdm1fcG9sX3R1bmUkYmVzdC5tb2RlbA0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDEsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjUsMCwxKQ0KbWVhbihwcmVkX3BvbCAgIT0gdGVzdCRwb3NpdGl2ZSkNCg0KcHJlZF9wb2wgPSBwcmVkaWN0KHN2bV9wb2wyLG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2wgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC41LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sMyxuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDQsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjUsMCwxKQ0KbWVhbihwcmVkX3BvbCAgIT0gdGVzdCRwb3NpdGl2ZSkNCmBgYA0KDQpDaGFuZ2luZyB0aGUgY29zdCBkaWQgbm90IGNoYW5nZSB0aGUgcmVzdWx0cyBtdWNoLiBMZXRzIHRyeSB2YXJ5aW5nIGdhbW1hLCB1c2luZyB0aGUgYmVzdCBjb3N0IGZvdW5kIGF0IDUuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjIyKQ0KDQpzdm1fcG9sMSA8LSBzdm0ocG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9InBvbHlub21pYWwiLCBkZWdyZWUgPSAxLCBjb3N0ID0gNSkNCnN2bV9wb2wyIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icG9seW5vbWlhbCIsIGRlZ3JlZSA9IDIsIGNvc3QgPSA1KQ0Kc3ZtX3BvbDMgPC0gc3ZtKHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJwb2x5bm9taWFsIiwgZGVncmVlID0gMywgY29zdCA9IDUpDQpzdm1fcG9sNCA8LSBzdm0ocG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9InBvbHlub21pYWwiLCBkZWdyZWUgPSA0LCBjb3N0ID01KQ0KDQojc3ZtX3BvbF90dW5lIDwtIHR1bmUoc3ZtLCBwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icG9seW5vbWlhbCIsIHJhbmdlcz1saXN0KGNvc3QgPSBjKDAuMSwxLDIsNSksIGdhbW1hID0gYygwLjUsMSwyLDMpKSkNCiNzdm1fcG9sX2Jlc3QgPC0gc3ZtX3BvbF90dW5lJGJlc3QubW9kZWwNCg0KcHJlZF9wb2wgPSBwcmVkaWN0KHN2bV9wb2wxLG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2wgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC41LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sMixuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDMsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjUsMCwxKQ0KbWVhbihwcmVkX3BvbCAgIT0gdGVzdCRwb3NpdGl2ZSkNCg0KcHJlZF9wb2wgPSBwcmVkaWN0KHN2bV9wb2w0LG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2wgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC41LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQpgYGANCg0KKiogU3VtbWFyaXplIHlvdXIgZmluZGluZ3MgKioNCg0KYGBge3J9DQpiZXN0X21vZGVsX3N2bSA9ICBzdm0ocG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9InBvbHlub21pYWwiLCBkZWdyZWUgPSAzLCBjb3N0ID0gNSkNCnN1bW1hcnkoYmVzdF9tb2RlbF9zdm0pDQoNCg0KcHJlZF9wb2wgPSBwcmVkaWN0KGJlc3RfbW9kZWxfc3ZtLG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2wgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC4yNSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KYGBgDQoNClRoZSBiZXN0IG1vZGVsIGZvdW5kIGhhcyBjb3N0IDUsIGN1dHMgYXQgMC4yNSwgYW5kIGhhcyBkZWdyZWUgMy4NClRoZSB0ZXN0IGVycm9yIGZvciBpdCBpcyAyNiUuDQoNCg0KDQojIFNWTSAtIFJhZGlhbA0KDQoNCmBgYHtyfQ0KI2luc3RhbGwucGFja2FnZXMoImUxMDcxIikNCmxpYnJhcnkoZTEwNzEpDQpzdm1fcmFkIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icmFkaWFsIikNCmBgYA0KDQpXaXRob3V0IGRvaW5nIGFueSB0dW5pbmcgbGVzdCBzZWUgdGhlIHRlc3QgZXJyb3IgZm9yIGEgcmFkaWFsIHN2bToNCg0KYGBge3J9DQpwcmVkX3JhZCA9IHByZWRpY3Qoc3ZtX3JhZCxuZXdkYXRhID0gdGVzdCkNCnByZWRfcmFkXzA1IDwtIGlmZWxzZShwcmVkX3JhZCA8IDAuNSwwLDEpDQptZWFuKHByZWRfcmFkXzA1ICAhPSB0ZXN0JHBvc2l0aXZlKQ0KYGBgDQoNClRoZSB0ZXN0IGVycm9yIGZvciByYWRpYWwga2VybmVsIHNlZW1zIG1vcmUgcHJvbWlzaW5nLiBXaXRob3V0IGFueSB0dW5pbmcgaXQgaXMgc28gZmFyIHRoZSBiZXN0IHRlc3QgZXJyb3IuIEV2ZW4gdGhvdWdoIGl0IGlzIHN0aWxsIHF1aXRlIGhpZ2ggYXQgMjUlDQoNCioqIHNob3cgYWxsIHN0ZXBzIGluIG1vZGVsIHNlbGVjdGlvbiBhcyB3ZWxsIGFzIHRoZSB0dW5pbmcgb2YgYW55IHBhcmFtZXRlcnMuICoqDQoNCkxldHMgZG8gc29tZSB0dW5pbmcgdG8gZ2V0IGEgYmV0dGVyIG1vZGVsLiBMZXRzIHR1bmUgdGhlIGNvc3QgYW5kIGdhbW1hIHZhcmlhYmxlczoNCg0KYGBge3J9DQpzZXQuc2VlZCgyMjIpDQoNCnN2bV9wb2wxIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icmFkaWFsIiwgY29zdCA9IDAuMSkNCnN2bV9wb2wyIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icmFkaWFsIiwgY29zdCA9IDAuNSkNCnN2bV9wb2wzIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icmFkaWFsIiwgY29zdCA9IDEpDQpzdm1fcG9sNCA8LSBzdm0ocG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9InJhZGlhbCIsIGNvc3QgPSA1KQ0KDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sMSxuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDIsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjUsMCwxKQ0KbWVhbihwcmVkX3BvbCAgIT0gdGVzdCRwb3NpdGl2ZSkNCg0KcHJlZF9wb2wgPSBwcmVkaWN0KHN2bV9wb2wzLG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2wgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC41LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sNCxuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KYGBgDQoNCkNoYW5naW5nIHRoZSBjb3N0IGRpZCBub3QgY2hhbmdlIHRoZSByZXN1bHRzIG11Y2guIExldHMgdHJ5IHZhcnlpbmcgZ2FtbWEsIHVzaW5nIHRoZSBiZXN0IGNvc3QgZm91bmQgYXQgMC41Lg0KDQpgYGB7cn0NCnNldC5zZWVkKDIyMikNCg0Kc3ZtX3BvbDEgPC0gc3ZtKHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJyYWRpYWwiLCBjb3N0ID0gMC41LCBnYW1tYSA9IDAuMSkNCnN2bV9wb2wyIDwtIHN2bShwb3NpdGl2ZSB+IC4sIGRhdGE9dHJhaW4sIGtlcm5lbD0icmFkaWFsIiwgY29zdCA9IDAuNSwgZ2FtbWEgPSAwLjIpDQpzdm1fcG9sMyA8LSBzdm0ocG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9InJhZGlhbCIsIGNvc3QgPSAwLjUsIGdhbW1hID0gMC4zKQ0Kc3ZtX3BvbDQgPC0gc3ZtKHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJyYWRpYWwiLCBjb3N0ID0gMC41LCBnYW1tYSA9IDAuNSkNCg0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDEsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjUsMCwxKQ0KbWVhbihwcmVkX3BvbCAgIT0gdGVzdCRwb3NpdGl2ZSkNCg0KcHJlZF9wb2wgPSBwcmVkaWN0KHN2bV9wb2wyLG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2wgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC41LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sMyxuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDQsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjUsMCwxKQ0KbWVhbihwcmVkX3BvbCAgIT0gdGVzdCRwb3NpdGl2ZSkNCmBgYA0KDQpUaGUgZGVmYXVsdCBnYW1tYSBzZWVtcyBsaWtlIHRoZSBiZXN0IG9wdGlvbi4NCg0KTGV0cyB0cnkgdmFyeWluZyB3aGVyZSB3ZSBjdXQ6DQoNCmBgYHtyfQ0Kc3ZtX3BvbDIgPC0gc3ZtKHBvc2l0aXZlIH4gLiwgZGF0YT10cmFpbiwga2VybmVsPSJyYWRpYWwiLCBjb3N0ID0gMC41KQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDIsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjI1LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sMixuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuMywwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDIsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjM1LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sMixuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNCwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDIsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjQ1LDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQoNCnByZWRfcG9sID0gcHJlZGljdChzdm1fcG9sMixuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNTAsMCwxKQ0KbWVhbihwcmVkX3BvbCAgIT0gdGVzdCRwb3NpdGl2ZSkNCg0KcHJlZF9wb2wgPSBwcmVkaWN0KHN2bV9wb2wyLG5ld2RhdGEgPSB0ZXN0KQ0KcHJlZF9wb2wgPC0gaWZlbHNlKHByZWRfcG9sIDwgMC41NSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KDQpwcmVkX3BvbCA9IHByZWRpY3Qoc3ZtX3BvbDIsbmV3ZGF0YSA9IHRlc3QpDQpwcmVkX3BvbCA8LSBpZmVsc2UocHJlZF9wb2wgPCAwLjYwLDAsMSkNCm1lYW4ocHJlZF9wb2wgICE9IHRlc3QkcG9zaXRpdmUpDQpgYGANCg0KVGhlIGJlc3QgY3V0IGZvdW5kIGlzIHN0aWxsIGF0IDAuNS4NCg0KKiogU3VtbWFyaXplIHlvdXIgZmluZGluZ3MgKioNCg0KYGBge3J9DQpiZXN0X21vZGVsX3N2bSA9ICBzdm0ocG9zaXRpdmUgfiAuLCBkYXRhPXRyYWluLCBrZXJuZWw9InJhZGlhbCIsIGNvc3QgPSAwLjUpDQpzdW1tYXJ5KGJlc3RfbW9kZWxfc3ZtKQ0KDQoNCnByZWRfcG9sID0gcHJlZGljdChiZXN0X21vZGVsX3N2bSxuZXdkYXRhID0gdGVzdCkNCnByZWRfcG9sIDwtIGlmZWxzZShwcmVkX3BvbCA8IDAuNSwwLDEpDQptZWFuKHByZWRfcG9sICAhPSB0ZXN0JHBvc2l0aXZlKQ0KYGBgDQoNCg0KVGhlIGJlc3QgbW9kZWwgZm91bmQgaGFzIGNvc3QgMC41LCBjdXRzIGF0IDAuNSwgYW5kIGhhcyB0aGUgZGVmYXVsdCBnYW1tYSBvZiAwLjA3LiBJdCBjcmVhdGVkIDIxOTAgc3VwcG9ydCB2ZWN0b3JzLiBUaGUgYmVzdCB0ZXN0IGVycm9yIGZvciBpdCBpcyAyNCw2JS4NCg0KKiogQ29tcGFyZSB0aGUgbGluZWFyLCBwb2x5bm9taWFsLCBhbmQgcmFkaWFsIGtlcm5lbHMuICoqDQoNCiogVGhlIGJlc3QgbGluZWFyIG1vZGVsOiAwLjM0DQoqIFRoZSBiZXN0IHBvbHlub21pYWwgbW9kZWw6IDAuMjQ5DQoqIFRoZSBiZXN0IHJhZGlhbCBtb2RlbDogMC4yNDYNCg0KT3V0IG9mIGFsbCB0aGUgbW9kZWxzLCB0aGUgcmFkaWFsIGtlcm5lbCB3b3JrZWQgdGhlIGJlc3QuDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=